Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ module.exports = {
'^@/convex/_generated/dataModel$': '<rootDir>/tests/mocks/convex-generated-dataModel.ts',
'^@/convex/(.*)$': '<rootDir>/convex/$1',
'^@/(.*)$': '<rootDir>/src/$1',
'^@inngest/agent-kit$': '<rootDir>/tests/mocks/inngest-agent-kit.ts',
'^@ai-sdk/gateway$': '<rootDir>/tests/mocks/ai-sdk-gateway.ts',
'^ai$': '<rootDir>/tests/mocks/ai.ts',
'^@e2b/code-interpreter$': '<rootDir>/tests/mocks/e2b-code-interpreter.ts',
'^convex/browser$': '<rootDir>/tests/mocks/convex-browser.ts',
},
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"convex:deploy": "bunx convex deploy"
},
"dependencies": {
"@ai-sdk/gateway": "^0.0.9",
"@databuddy/sdk": "^2.2.1",
"@e2b/code-interpreter": "^1.5.1",
"@hookform/resolvers": "^3.3.4",
"@inngest/agent-kit": "^0.13.1",
"@inngest/realtime": "^0.4.4",
"ai": "^4.3.16",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/core": "^2.2.0",
"@opentelemetry/resources": "^2.2.0",
Expand Down
8 changes: 8 additions & 0 deletions src/ai-sdk/gateway.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createGateway } from "@ai-sdk/gateway";

export const gateway = createGateway({
baseURL: process.env.AI_GATEWAY_BASE_URL || "https://gateway.ai.vercel.sh/v1",
headers: {
Authorization: `Bearer ${process.env.AI_GATEWAY_API_KEY}`,
},
});
9 changes: 9 additions & 0 deletions src/ai-sdk/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export { gateway } from "./gateway";
export { createCodeAgentTools } from "./tools";
export type {
AgentState,
AgentResult,
Framework,
AgentContext,
ToolResult,
} from "./types";
137 changes: 137 additions & 0 deletions src/ai-sdk/tools.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { tool } from "ai";
import { z } from "zod";
import { Sandbox } from "@e2b/code-interpreter";
import type { AgentState, ToolResult } from "./types";

async function getSandboxFromId(sandboxId: string): Promise<Sandbox> {
return await Sandbox.connect(sandboxId, {
apiKey: process.env.E2B_API_KEY,
});
}

export function createCodeAgentTools(
sandboxId: string,
stateRef: { current: AgentState }
) {
const terminalTool = tool({
description: "Run a terminal command in the sandbox environment",
parameters: z.object({
command: z.string().describe("The command to execute in the terminal"),
}),
execute: async ({ command }): Promise<ToolResult> => {
const buffers = { stdout: "", stderr: "" };

try {
const sandbox = await getSandboxFromId(sandboxId);
const result = await sandbox.commands.run(command, {
onStdout: (data: string) => {
buffers.stdout += data;
},
onStderr: (data: string) => {
buffers.stderr += data;
},
});
return {
success: true,
data: {
stdout: result.stdout || buffers.stdout,
stderr: buffers.stderr,
exitCode: result.exitCode,
},
};
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
return {
success: false,
error: `Command failed: ${errorMessage}\nstdout: ${buffers.stdout}\nstderr: ${buffers.stderr}`,
};
}
},
});

const createOrUpdateFilesTool = tool({
description:
"Create or update files in the sandbox. Use this to write code files.",
parameters: z.object({
files: z
.array(
z.object({
path: z.string().describe("The file path relative to the sandbox"),
content: z.string().describe("The content to write to the file"),
})
)
.describe("Array of files to create or update"),
}),
execute: async ({ files }): Promise<ToolResult> => {
try {
const sandbox = await getSandboxFromId(sandboxId);
const updatedFiles = stateRef.current.files || {};

for (const file of files) {
await sandbox.files.write(file.path, file.content);
updatedFiles[file.path] = file.content;
}

stateRef.current.files = updatedFiles;

return {
success: true,
data: {
filesWritten: files.map((f) => f.path),
totalFiles: Object.keys(updatedFiles).length,
},
};
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
return {
success: false,
error: `Failed to write files: ${errorMessage}`,
};
}
},
});

const readFilesTool = tool({
description: "Read files from the sandbox to understand existing code",
parameters: z.object({
files: z
.array(z.string())
.describe("Array of file paths to read from the sandbox"),
}),
execute: async ({ files }): Promise<ToolResult> => {
try {
const sandbox = await getSandboxFromId(sandboxId);
const contents: Array<{ path: string; content: string }> = [];

for (const filePath of files) {
try {
const content = await sandbox.files.read(filePath);
contents.push({ path: filePath, content });
} catch {
contents.push({
path: filePath,
content: `[Error: Could not read file ${filePath}]`,
});
}
}

return {
success: true,
data: contents,
};
} catch (e) {
const errorMessage = e instanceof Error ? e.message : String(e);
return {
success: false,
error: `Failed to read files: ${errorMessage}`,
};
}
},
});

return {
terminal: terminalTool,
createOrUpdateFiles: createOrUpdateFilesTool,
readFiles: readFilesTool,
};
}
28 changes: 28 additions & 0 deletions src/ai-sdk/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { CoreMessage } from "ai";

export type Framework = "nextjs" | "angular" | "react" | "vue" | "svelte";

export interface AgentState {
summary: string;
files: Record<string, string>;
selectedFramework?: Framework;
summaryRetryCount: number;
}

export interface AgentContext {
sandboxId: string;
state: AgentState;
messages: CoreMessage[];
}

export interface AgentResult {
state: AgentState;
messages: CoreMessage[];
output: string;
}

export interface ToolResult {
success: boolean;
data?: unknown;
error?: string;
}
4 changes: 2 additions & 2 deletions src/app/api/agent/token/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ export async function POST() {
);
}

// Realtime token generation is not available without @inngest/realtime middleware
// TODO: Install @inngest/realtime if needed
// Realtime token generation is handled via AI SDK streaming
// This endpoint is a placeholder for future realtime features
return Response.json(
{ error: "Realtime feature not configured" },
{ status: 503 }
Expand Down
Loading
Loading