Skip to content
This repository was archived by the owner on Mar 25, 2026. It is now read-only.

Commit d013b39

Browse files
devin-ai-integration[bot]afterrburnafterrburn
authored
Empty PR 6 (#295)
* Empty PR 6 - minimal change for testing purposes Co-Authored-By: srith@agentuity.com <rithsenghorn@gmail.com> * add tutorial APIs --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: srith@agentuity.com <rithsenghorn@gmail.com> Co-authored-by: afterrburn <sun_rsh@outlook.com>
1 parent 5af5b24 commit d013b39

18 files changed

Lines changed: 622 additions & 280 deletions

File tree

‎agent-docs/src/agents/agent-pulse/index.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ function buildCurrentTutorialInfo(
7373
*/
7474
function defaultFallbackContext(): string {
7575
return `===AVAILABLE TUTORIALS====
76-
Unable to load tutorial list. Please try again later or contact support.`;
76+
Unable to load tutorial list currently`;
7777
}
7878

7979
export default async function Agent(

‎agent-docs/src/agents/agent-pulse/request/parser.ts‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,8 @@ export function parseAgentRequest(
1515
const body = jsonData as any;
1616
message = body.message || "";
1717
useDirectLLM = body.use_direct_llm || false;
18-
// Process conversation history
1918
if (Array.isArray(body.conversationHistory)) {
2019
conversationHistory = body.conversationHistory.map((msg: any) => {
21-
// Extract only role and content
2220
return {
2321
role: msg.role || (msg.author ? msg.author.toUpperCase() : "USER"),
2422
content: msg.content || "",
@@ -28,7 +26,6 @@ export function parseAgentRequest(
2826

2927
tutorialData = body.tutorialData || undefined;
3028
} else {
31-
// Fallback for non-object data
3229
message = String(jsonData || "");
3330
}
3431

‎agent-docs/src/agents/agent-pulse/state/manager.ts‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,21 @@ export async function handleTutorialState(
3030
totalSteps: action.totalSteps,
3131
currentStep: action.currentStep,
3232
tutorialStep: {
33-
title: (tutorialStep.data.meta?.title as string) || tutorialStep.data.slug,
34-
mdx: tutorialStep.data.mdx,
35-
snippets: tutorialStep.data.snippets,
36-
totalSteps: action.totalSteps
33+
title: tutorialStep.data.tutorialStep.title,
34+
mdx: tutorialStep.data.tutorialStep.mdx,
35+
snippets: tutorialStep.data.tutorialStep.snippets,
36+
totalSteps: tutorialStep.data.tutorialStep.totalSteps
3737
}
3838
};
3939
state.clearAction();
4040
ctx.logger.info("Tutorial state processed successfully");
4141
return tutorialData;
42+
} else {
43+
// Handle API errors gracefully
44+
ctx.logger.error("Failed to fetch tutorial step: %s", tutorialStep.error || 'Unknown error');
45+
if (tutorialStep.details) {
46+
ctx.logger.error("Error details: %s", JSON.stringify(tutorialStep.details));
47+
}
4248
}
4349
}
4450
break;

‎agent-docs/src/agents/agent-pulse/tools.ts‎

Lines changed: 3 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export async function createTools(context: ToolContext) {
2424
* Tool for starting a tutorial - adds action to state queue
2525
*/
2626
const startTutorialAtStep = tool({
27-
description: "Start a specific tutorial for the user. You must call this function in order for the user to see the tutorial step content. The step number should be between 1 and the total number of steps in the tutorial.",
27+
description: "Start a specific tutorial for the user. You must call this function in order for the user to see the tutorial step content. The tutorial content will be injected to the final response automatically -- you do not have to narrate the tutorial content. The step number should be between 1 and the total number of steps in the tutorial.",
2828
parameters: z.object({
2929
tutorialId: z.string().describe("The exact ID of the tutorial to start"),
3030
stepNumber: z.number().describe("The step number of the tutorial to start (1 to total available steps in the tutorial)")
@@ -53,7 +53,7 @@ export async function createTools(context: ToolContext) {
5353
});
5454

5555
/**
56-
* Tool for talking to other agents (nong-tutorial functionality)
56+
* Tool that talks to docs agent.
5757
* This tool doesn't use state - it returns data directly
5858
*/
5959
const askDocsAgentTool = tool({
@@ -71,28 +71,12 @@ export async function createTools(context: ToolContext) {
7171
data: agentPayload,
7272
contentType: 'application/json'
7373
})
74-
// TODO: handle the docs referencing and inject it to the frontend response
74+
// TODO: handle the docs referencing and inject it to the frontend response
7575
const responseData = await response.data.json();
7676
return responseData;
7777
},
7878
});
7979

80-
/**
81-
* TODO: This tool allow the agent to get details information about the code execution that the user performed.
82-
*/
83-
const fetchCodeExecutionResultTool = tool({
84-
description: "Fetch code execution results from the frontend",
85-
parameters: z.object({
86-
executionId: z.string().describe("The ID of the code execution"),
87-
}),
88-
execute: async ({ executionId }) => {
89-
agentContext.logger.info("Fetching execution result for: %s", executionId);
90-
// This would actually fetch execution results
91-
// For now, just return a mock response
92-
return `Result for execution ${executionId}: console.log('Hello, World!');\n// Output: Hello, World!`;
93-
},
94-
});
95-
9680
// Return tools object
9781
return {
9882
startTutorialById: startTutorialAtStep,

‎agent-docs/src/agents/agent-pulse/tutorial.ts‎

Lines changed: 92 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,15 @@ export interface Tutorial {
77
title: string;
88
description: string;
99
totalSteps: number;
10+
difficulty?: string;
11+
estimatedTime?: string;
1012
}
1113

1214
interface ApiResponse<T> {
1315
success: boolean;
1416
data?: T;
1517
error?: string;
18+
details?: any;
1619
status?: number;
1720
}
1821

@@ -27,21 +30,30 @@ export interface TutorialSnippet {
2730

2831
interface TutorialStepResponseData {
2932
tutorialId: string;
30-
stepNumber: number;
31-
slug: string;
32-
meta: Record<string, unknown>;
33-
mdx: string;
34-
snippets: TutorialSnippet[];
33+
totalSteps: number;
34+
currentStep: number;
35+
tutorialStep: {
36+
title: string;
37+
mdx: string;
38+
snippets: TutorialSnippet[];
39+
totalSteps: number;
40+
estimatedTime?: string;
41+
};
3542
}
3643

3744
export async function getTutorialList(ctx: AgentContext): Promise<ApiResponse<Tutorial[]>> {
3845
try {
3946
const response = await fetch(`${TUTORIAL_API_BASE_URL}/api/tutorials`);
4047

4148
if (!response.ok) {
42-
const error = await response.json();
43-
ctx.logger.error('Tutorial API error: %s', error);
44-
throw new Error(`Tutorial API error: ${JSON.stringify(error)}`);
49+
const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string; details?: any };
50+
ctx.logger.error('Tutorial API error: %s', JSON.stringify(errorData));
51+
return {
52+
success: false,
53+
status: response.status,
54+
error: errorData.error || response.statusText,
55+
details: errorData.details
56+
};
4557
}
4658

4759
const tutorials = await response.json() as Tutorial[];
@@ -53,28 +65,66 @@ export async function getTutorialList(ctx: AgentContext): Promise<ApiResponse<Tu
5365
};
5466
} catch (error) {
5567
ctx.logger.error('Error fetching tutorial list: %s', error);
56-
throw error;
68+
return {
69+
success: false,
70+
error: `Network error: ${error instanceof Error ? error.message : String(error)}`
71+
};
5772
}
5873
}
5974

6075
export async function getTutorialMeta(tutorialId: string, ctx: AgentContext): Promise<ApiResponse<Tutorial>> {
6176
try {
62-
// New behavior: fetch all, then find by id
63-
const response = await fetch(`${TUTORIAL_API_BASE_URL}/api/tutorials`);
77+
const response = await fetch(`${TUTORIAL_API_BASE_URL}/api/tutorials/${tutorialId}`);
78+
6479
if (!response.ok) {
65-
const error = `Failed to fetch tutorial list: ${response.statusText}`;
66-
ctx.logger.error(error);
67-
return { success: false, status: response.status, error: response.statusText };
80+
const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string; details?: any };
81+
ctx.logger.error('Failed to fetch tutorial %s: %s', tutorialId, JSON.stringify(errorData));
82+
return {
83+
success: false,
84+
status: response.status,
85+
error: errorData.error || response.statusText,
86+
details: errorData.details
87+
};
6888
}
69-
const tutorials = (await response.json()) as Tutorial[];
70-
const found = tutorials.find(t => t.id === tutorialId);
71-
if (!found) {
72-
return { success: false, status: 404, error: 'Tutorial not found' };
89+
90+
const responseData = await response.json() as ApiResponse<{
91+
id: string;
92+
metadata: {
93+
title: string;
94+
description: string;
95+
totalSteps: number;
96+
difficulty?: string;
97+
estimatedTime?: string;
98+
};
99+
fullContent: string;
100+
steps: Array<{ stepNumber: number; title: string; estimatedTime?: string; }>;
101+
}>;
102+
103+
if (!responseData.success || !responseData.data) {
104+
return {
105+
success: false,
106+
error: responseData.error || 'Invalid response format'
107+
};
73108
}
74-
return { success: true, data: found };
109+
110+
// Convert to Tutorial format for backward compatibility
111+
const tutorial: Tutorial = {
112+
id: responseData.data.id,
113+
title: responseData.data.metadata.title,
114+
description: responseData.data.metadata.description,
115+
totalSteps: responseData.data.metadata.totalSteps,
116+
difficulty: responseData.data.metadata.difficulty,
117+
estimatedTime: responseData.data.metadata.estimatedTime
118+
};
119+
120+
ctx.logger.info('Fetched tutorial metadata for %s', tutorialId);
121+
return { success: true, data: tutorial };
75122
} catch (error) {
76123
ctx.logger.error('Error fetching tutorial metadata for %s: %s', tutorialId, error);
77-
throw error;
124+
return {
125+
success: false,
126+
error: `Network error: ${error instanceof Error ? error.message : String(error)}`
127+
};
78128
}
79129
}
80130

@@ -83,16 +133,32 @@ export async function getTutorialStep(tutorialId: string, stepNumber: number, ct
83133
const response = await fetch(`${TUTORIAL_API_BASE_URL}/api/tutorials/${tutorialId}/steps/${stepNumber}`);
84134

85135
if (!response.ok) {
86-
ctx.logger.error('Failed to fetch tutorial step %d for tutorial %s: %s', stepNumber, tutorialId, response.statusText);
87-
return { success: false, status: response.status, error: response.statusText };
136+
const errorData = await response.json().catch(() => ({ error: response.statusText })) as { error?: string; details?: any };
137+
ctx.logger.error('Failed to fetch tutorial step %d for tutorial %s: %s', stepNumber, tutorialId, JSON.stringify(errorData));
138+
return {
139+
success: false,
140+
status: response.status,
141+
error: errorData.error || response.statusText,
142+
details: errorData.details
143+
};
88144
}
89145

90-
const responseData = await response.json();
91-
ctx.logger.info('Fetched step %d for tutorial %s', stepNumber, tutorialId);
146+
const responseData = await response.json() as ApiResponse<TutorialStepResponseData>;
147+
148+
if (!responseData.success || !responseData.data) {
149+
return {
150+
success: false,
151+
error: responseData.error || 'Invalid response format'
152+
};
153+
}
92154

93-
return responseData as ApiResponse<TutorialStepResponseData>;
155+
ctx.logger.info('Fetched step %d for tutorial %s', stepNumber, tutorialId);
156+
return responseData;
94157
} catch (error) {
95158
ctx.logger.error('Error fetching tutorial step %d for tutorial %s: %s', stepNumber, tutorialId, error);
96-
throw error;
159+
return {
160+
success: false,
161+
error: `Network error: ${error instanceof Error ? error.message : String(error)}`
162+
};
97163
}
98164
}

‎app/(docs)/[[...slug]]/page.tsx‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { CommunityButton } from '../../../components/Community';
2020
import CopyPageDropdown from '../../../components/CopyPageDropdown';
2121
import { NavButton } from '../../../components/NavButton';
2222
import CodeFromFiles from '../../../components/CodeFromFiles';
23+
import TutorialStep from '../../../components/TutorialStep';
2324

2425

2526
export default async function Page(props: {
@@ -67,6 +68,7 @@ export default async function Page(props: {
6768
Tab,
6869
Tabs,
6970
ThemeImage,
71+
TutorialStep,
7072
TypingAnimation,
7173
}}
7274
/>

‎app/api/tutorials/[id]/route.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { join } from 'path';
3+
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
4+
import { TutorialIdParamsSchema } from '@/lib/tutorial/schemas';
5+
6+
interface RouteParams {
7+
params: Promise<{ id: string }>;
8+
}
9+
10+
export async function GET(request: NextRequest, { params }: RouteParams) {
11+
try {
12+
const rawParams = await params;
13+
14+
// Validate parameters with Zod
15+
const validationResult = TutorialIdParamsSchema.safeParse(rawParams);
16+
if (!validationResult.success) {
17+
return NextResponse.json(
18+
{ success: false, error: 'Invalid parameters', details: validationResult.error.format() },
19+
{ status: 400 }
20+
);
21+
}
22+
23+
const { id } = validationResult.data;
24+
const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);
25+
26+
const parsed = await parseTutorialMDXCached(filePath);
27+
28+
return NextResponse.json({
29+
success: true,
30+
data: {
31+
id,
32+
metadata: parsed.metadata,
33+
fullContent: parsed.fullContent,
34+
steps: parsed.steps.map(step => ({
35+
stepNumber: step.stepNumber,
36+
title: step.title,
37+
estimatedTime: step.estimatedTime,
38+
}))
39+
}
40+
});
41+
} catch (error) {
42+
console.error(`Failed to load tutorial:`, error);
43+
return NextResponse.json(
44+
{ success: false, error: 'Tutorial not found' },
45+
{ status: 404 }
46+
);
47+
}
48+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { join } from 'path';
3+
import { parseTutorialMDXCached } from '@/lib/tutorial/mdx-parser';
4+
import { StepParamsSchema } from '@/lib/tutorial/schemas';
5+
6+
interface RouteParams {
7+
params: Promise<{ id: string; stepNumber: string }>;
8+
}
9+
10+
export async function GET(request: NextRequest, { params }: RouteParams) {
11+
try {
12+
const rawParams = await params;
13+
14+
// Validate and transform parameters with Zod
15+
const validationResult = StepParamsSchema.safeParse(rawParams);
16+
if (!validationResult.success) {
17+
return NextResponse.json(
18+
{ success: false, error: 'Invalid parameters', details: validationResult.error.format() },
19+
{ status: 400 }
20+
);
21+
}
22+
23+
const { id, stepNumber: stepNum } = validationResult.data;
24+
25+
const filePath = join(process.cwd(), 'content', 'Tutorial', `${id}.mdx`);
26+
const parsed = await parseTutorialMDXCached(filePath);
27+
28+
const step = parsed.steps.find(s => s.stepNumber === stepNum);
29+
if (!step) {
30+
return NextResponse.json(
31+
{ success: false, error: 'Step not found' },
32+
{ status: 404 }
33+
);
34+
}
35+
36+
return NextResponse.json({
37+
success: true,
38+
data: {
39+
tutorialId: id,
40+
totalSteps: parsed.metadata.totalSteps,
41+
currentStep: stepNum,
42+
tutorialStep: {
43+
title: step.title,
44+
mdx: step.mdxContent,
45+
snippets: step.snippets,
46+
totalSteps: parsed.metadata.totalSteps,
47+
estimatedTime: step.estimatedTime,
48+
}
49+
}
50+
});
51+
} catch (error) {
52+
console.error(`Failed to load tutorial step:`, error);
53+
return NextResponse.json(
54+
{ success: false, error: 'Failed to load step' },
55+
{ status: 500 }
56+
);
57+
}
58+
}

0 commit comments

Comments
 (0)