Skip to content

Migrate to AI SDK Gateway#151

Closed
Jackson57279 wants to merge 1 commit intomasterfrom
tembo/remove-inngest-ai-gateway
Closed

Migrate to AI SDK Gateway#151
Jackson57279 wants to merge 1 commit intomasterfrom
tembo/remove-inngest-ai-gateway

Conversation

@Jackson57279
Copy link
Owner

@Jackson57279 Jackson57279 commented Nov 29, 2025

Description

Migrates agent functionality from Inngest Gateway to Vercel's AI Gateway.

Changes

Replaced Inngest agent kit with AI SDK for agent orchestration.


Want me to make any changes? Add a review or comment with @tembo and i'll get back to work!

tembo.io app.tembo.io

Summary by CodeRabbit

  • Chores
    • Migrated AI agent infrastructure to a new SDK backend with improved gateway support.
    • Updated dependencies to streamline agent-powered tooling.
    • Refactored internal state management and messaging systems for better agent execution.
    • Updated test mocks to align with new AI SDK architecture.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Nov 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
zapdev Error Error Nov 29, 2025 8:40am

@codecapyai
Copy link

codecapyai bot commented Nov 29, 2025

CodeCapy Review ₍ᐢ•(ܫ)•ᐢ₎

Codebase Summary

ZapDev is an AI-powered development platform that allows users to create and iterate on web applications using real-time AI agents within sandboxes. It features code generation, file management, project persistence, and integrates various services such as Vercel AI Gateway, Clerk authentication, and background job processing.

PR Changes

This pull request migrates the agent functionality from the Inngest Gateway to Vercel's AI Gateway by replacing the '@inngest/agent-kit' references with '@ai-sdk/gateway' and related modules. The changes include updates in jest configuration, dependency changes in package.json, and modifications in the agent functions and tools to work with the new AI SDK.

Setup Instructions

  1. Install pnpm globally if not already installed: sudo npm install -g pnpm
  2. Clone the repository and navigate into it: cd
  3. Install dependencies: pnpm install
  4. Set up your environment variables by copying env.example: cp env.example .env, then fill in the required API keys and database URL
  5. Build the required E2B template as described in the README
  6. Start the development server: pnpm dev
  7. Open your browser and navigate to http://localhost:3000 to interact with the application

Generated Test Cases

1: Agent Request Flow Using AI SDK Gateway ❗️❗️❗️

Description: Verifies that when a user submits a new development request, the request is processed through the updated AI SDK Gateway and the generated code preview appears. This tests the full end-to-end flow of initiating an AI agent call and displaying outputs.

Prerequisites:

  • User is logged in
  • Sandbox environment is set up (with a valid AI_GATEWAY_API_KEY and AI_GATEWAY_BASE_URL in .env)

Steps:

  1. Start the development server (pnpm dev) and open http://localhost:3000 in a browser.
  2. Navigate to the project dashboard or the page where users input their new project requests.
  3. Enter a sample request such as 'Build a landing page with a contact form.' in the provided input field.
  4. Click the 'Generate' or 'Submit' button to send the request.
  5. Watch the live preview pane as the system processes the request via the AI SDK Gateway.
  6. Observe that the agent begins its processing, indicated by a loading spinner or progress indicator.
  7. Wait until the generated code appears in the code preview area and a summary of the task is displayed.

Expected Result: The user sees a smooth transition from loading to displaying the generated code files and a summary description of the generated project, indicating that the AI request was routed correctly through the new AI SDK Gateway.

2: Error Handling When AI Gateway Request Fails ❗️❗️❗️

Description: Tests that if the AI Gateway returns an error (e.g., due to an incorrect API key), the UI correctly displays an error message informing the user of the issue.

Prerequisites:

  • User is logged in
  • Environment is configured with an invalid or missing AI_GATEWAY_API_KEY in the .env file

Steps:

  1. Start the development server (pnpm dev) and navigate to http://localhost:3000.
  2. Navigate to the agent interaction page where the project request can be submitted.
  3. Enter a sample project request and click the 'Submit' button.
  4. Observe the agent’s progress indicator; after a short delay, look for an error message in the UI.
  5. Check that the error message clearly states that there is an issue with the AI Gateway connection or configuration.
  6. Optionally, correct the API key and repeat the test to verify the error message disappears.

Expected Result: The UI presents an error notification indicating a failure in connecting to the AI Gateway, with clear instructions or hints for configuration. Once the configuration is corrected, the error message should be resolved.

3: Display of Updated File Explorer After AI Tool Execution ❗️❗️

Description: Ensures that when an AI agent generates or updates files via the new code agent tools, the file explorer in the UI is updated correctly to reflect the new/modified files.

Prerequisites:

  • User is logged in
  • A project and corresponding sandbox are active
  • Valid configuration for E2B and Vercel AI Gateway in .env

Steps:

  1. Start the development server (pnpm dev) and open http://localhost:3000 in a browser.
  2. Navigate to the project where the AI agent is available and a file explorer component is visible.
  3. Submit a project request (e.g., 'Create a new header component with navigation links') so that the AI agent generates files using its tools.
  4. Wait for the AI agent to complete processing and monitor the progress indicator.
  5. After processing, observe that the file explorer now shows a new file entry (or updated content) corresponding to the generated header component.
  6. Click on the new file entry to view its content and verify that it matches the expected structure.

Expected Result: The file explorer dynamically reflects the changes made by the AI agent. The new or updated files are visible with correct file names and content, and a summary description is available elsewhere on the page that confirms the agent’s action.

Raw Changes Analyzed
File: jest.config.js
Changes:
@@ -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',
   },

File: package.json
Changes:
@@ -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",

File: src/ai-sdk/gateway.ts
Changes:
@@ -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}`,
+  },
+});

File: src/ai-sdk/index.ts
Changes:
@@ -0,0 +1,9 @@
+export { gateway } from "./gateway";
+export { createCodeAgentTools } from "./tools";
+export type {
+  AgentState,
+  AgentResult,
+  Framework,
+  AgentContext,
+  ToolResult,
+} from "./types";

File: src/ai-sdk/tools.ts
Changes:
@@ -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,
+  };
+}

File: src/ai-sdk/types.ts
Changes:
@@ -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;
+}

File: src/app/api/agent/token/route.ts
Changes:
@@ -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 }

File: src/inngest/functions.ts
Changes:
@@ -1,21 +1,13 @@
-import { z } from "zod";
 import { Sandbox } from "@e2b/code-interpreter";
-import {
-  openai,
-  gemini,
-  createAgent,
-  createTool,
-  createNetwork,
-  type Tool,
-  type Message,
-  createState,
-  type NetworkRun,
-} from "@inngest/agent-kit";
+import { generateText, type CoreMessage } from "ai";
 import { ConvexHttpClient } from "convex/browser";
 import { api } from "@/convex/_generated/api";
 import type { Id } from "@/convex/_generated/dataModel";
 import { inspect } from "util";
 
+import { gateway } from "@/ai-sdk/gateway";
+import { createCodeAgentTools as createAISDKTools } from "@/ai-sdk/tools";
+import type { AgentState } from "@/ai-sdk/types";
 import { crawlUrl, type CrawledContent } from "@/lib/firecrawl";
 
 // Get Convex client lazily to avoid build-time errors
@@ -49,11 +41,9 @@ import {
 } from "@/prompt";
 
 import { inngest } from "./client";
-import { SANDBOX_TIMEOUT, type Framework, type AgentState } from "./types";
+import { SANDBOX_TIMEOUT, type Framework } from "./types";
 import {
   getSandbox,
-  lastAssistantTextMessageContent,
-  parseAgentOutput,
   createSandboxWithRetry,
   validateSandboxHealth,
 } from "./utils";
@@ -213,67 +203,6 @@ export function selectModelForTask(
   return chosenModel;
 }
 
-/**
- * Returns the appropriate AI adapter based on model provider
- */
-function getModelAdapter(
-  modelId: keyof typeof MODEL_CONFIGS | string,
-  temperature?: number,
-) {
-  const config =
-    modelId in MODEL_CONFIGS
-      ? MODEL_CONFIGS[modelId as keyof typeof MODEL_CONFIGS]
-      : null;
-
-  const commonConfig = {
-    model: modelId,
-    apiKey: process.env.AI_GATEWAY_API_KEY!,
-    baseUrl:
-      process.env.AI_GATEWAY_BASE_URL || "https://ai-gateway.vercel.sh/v1",
-    defaultParameters: {
-      temperature: temperature ?? config?.temperature ?? 0.7,
-    },
-  };
-
-  // Use native Gemini adapter for Google models (detect by model ID or provider)
-  const isGoogleModel =
-    config?.provider === "google" ||
-    modelId.startsWith("google/") ||
-    modelId.includes("gemini");
-
-  if (isGoogleModel) {
-    return gemini(commonConfig);
-  }
-
-  // Use OpenAI adapter for all other models (OpenAI, Anthropic, Moonshot, xAI, etc.)
-  return openai(commonConfig);
-}
-
-/**
- * Converts screenshot URLs to AI-compatible image messages
- */
-async function createImageMessages(screenshots: string[]): Promise<Message[]> {
-  const imageMessages: Message[] = [];
-
-  for (const screenshotUrl of screenshots) {
-    try {
-      // For URL-based images (OpenAI and Gemini support this)
-      imageMessages.push({
-        type: "image",
-        role: "user",
-        content: screenshotUrl,
-      });
-    } catch (error) {
-      console.error(
-        `[ERROR] Failed to create image message for ${screenshotUrl}:`,
-        error,
-      );
-    }
-  }
-
-  return imageMessages;
-}
-
 const AUTO_FIX_ERROR_PATTERNS = [
   /Error:/i,
   /\[ERROR\]/i,
@@ -360,17 +289,23 @@ const extractSummaryText = (value: string): string => {
 };
 
 const getLastAssistantMessage = (
-  networkRun: NetworkRun<AgentState>,
+  messages: CoreMessage[],
 ): string | undefined => {
-  const results = networkRun.state.results;
-
-  if (results.length === 0) {
-    return undefined;
+  for (let i = messages.length - 1; i >= 0; i--) {
+    const msg = messages[i];
+    if (msg.role === "assistant") {
+      if (typeof msg.content === "string") {
+        return msg.content;
+      }
+      if (Array.isArray(msg.content)) {
+        return msg.content
+          .filter((part) => part.type === "text")
+          .map((part) => (part as { type: "text"; text: string }).text)
+          .join("");
+      }
+    }
   }
-
-  const latestResult = results[results.length - 1];
-
-  return lastAssistantTextMessageContent(latestResult);
+  return undefined;
 };
 
 const runLintCheck = async (sandboxId: string): Promise<string | null> => {
@@ -787,98 +722,7 @@ const validateMergeStrategy = (
   };
 };
 
-const createCodeAgentTools = (sandboxId: string) => [
-  createTool({
-    name: "terminal",
-    description: "Use the terminal to run commands",
-    parameters: z.object({
-      command: z.string(),
-    }),
-    handler: async (
-      { command }: { command: string },
-      opts: Tool.Options<AgentState>,
-    ) => {
-      return await opts.step?.run("terminal", async () => {
-        const buffers: { stdout: string; stderr: string } = {
-          stdout: "",
-          stderr: "",
-        };
-
-        try {
-          const sandbox = await getSandbox(sandboxId);
-          const result = await sandbox.commands.run(command, {
-            onStdout: (data: string) => {
-              buffers.stdout += data;
-            },
-            onStderr: (data: string) => {
-              buffers.stderr += data;
-            },
-          });
-          return result.stdout;
-        } catch (e) {
-          console.error(
-            `Command failed: ${e} \nstdout: ${buffers.stdout}\nstderror: ${buffers.stderr}`,
-          );
-          return `Command failed: ${e} \nstdout: ${buffers.stdout}\nstderr: ${buffers.stderr}`;
-        }
-      });
-    },
-  }),
-  createTool({
-    name: "createOrUpdateFiles",
-    description: "Create or update files in the sandbox",
-    parameters: z.object({
-      files: z.array(
-        z.object({
-          path: z.string(),
-          content: z.string(),
-        }),
-      ),
-    }),
-    handler: async ({ files }, { step, network }: Tool.Options<AgentState>) => {
-      const newFiles = await step?.run("createOrUpdateFiles", async () => {
-        try {
-          const updatedFiles = network.state.data.files || {};
-          const sandbox = await getSandbox(sandboxId);
-          for (const file of files) {
-            await sandbox.files.write(file.path, file.content);
-            updatedFiles[file.path] = file.content;
-          }
-
-          return updatedFiles;
-        } catch (e) {
-          return "Error: " + e;
-        }
-      });
-
-      if (typeof newFiles === "object") {
-        network.state.data.files = newFiles;
-      }
-    },
-  }),
-  createTool({
-    name: "readFiles",
-    description: "Read files from the sandbox",
-    parameters: z.object({
-      files: z.array(z.string()),
-    }),
-    handler: async ({ files }, { step }) => {
-      return await step?.run("readFiles", async () => {
-        try {
-          const sandbox = await getSandbox(sandboxId);
-          const contents = [];
-          for (const file of files) {
-            const content = await sandbox.files.read(file);
-            contents.push({ path: file, content });
-          }
-          return JSON.stringify(contents);
-        } catch (e) {
-          return "Error: " + e;
-        }
-      });
-    },
-  }),
-];
+// Tools are now created using @ai-sdk tools via createAISDKTools from "@/ai-sdk/tools"
 
 export const codeAgentFunction = inngest.createFunction(
   { id: "code-agent" },
@@ -913,36 +757,22 @@ export const codeAgentFunction = inngest.createFunction(
     if (!project?.framework) {
       console.log("[DEBUG] No framework set, running framework selector...");
 
-      const frameworkSelectorAgent = createAgent({
-        name: "framework-selector",
-        description: "Determines the best framework for the user's request",
+      const frameworkResult = await generateText({
+        model: gateway("google/gemini-2.5-flash-lite"),
         system: FRAMEWORK_SELECTOR_PROMPT,
-        model: getModelAdapter("google/gemini-2.5-flash-lite", 0.3),
+        messages: [{ role: "user", content: event.data.value }],
+        temperature: 0.3,
       });
 
-      const frameworkResult = await frameworkSelectorAgent.run(
-        event.data.value,
-      );
-      const frameworkOutput = frameworkResult.output[0];
+      const detectedFramework = frameworkResult.text.trim().toLowerCase();
+      console.log("[DEBUG] Framework selector output:", detectedFramework);
 
-      if (frameworkOutput.type === "text") {
-        const detectedFramework = (
-          typeof frameworkOutput.content === "string"
-            ? frameworkOutput.content
-            : frameworkOutput.content.map((c) => c.text).join("")
+      if (
+        ["nextjs", "angular", "react", "vue", "svelte"].includes(
+          detectedFramework,
         )
-          .trim()
-          .toLowerCase();
-
-        console.log("[DEBUG] Framework selector output:", detectedFramework);
-
-        if (
-          ["nextjs", "angular", "react", "vue", "svelte"].includes(
-            detectedFramework,
-          )
-        ) {
-          selectedFramework = detectedFramework as Framework;
-        }
+      ) {
+        selectedFramework = detectedFramework as Framework;
       }
 
       console.log("[DEBUG] Selected framework:", selectedFramework);
@@ -1176,7 +1006,7 @@ export const codeAgentFunction = inngest.createFunction(
           "[DEBUG] Fetching previous messages for project:",
           event.data.projectId,
         );
-        const formattedMessages: Message[] = [];
+        const formattedMessages: CoreMessage[] = [];
 
         try {
           const allMessages = await convex.query(api.messages.listForUser, {
@@ -1191,7 +1021,6 @@ export const codeAgentFunction = inngest.createFunction(
 
           for (const message of messages) {
             formattedMessages.push({
-              type: "text",
               role: message.role === "ASSISTANT" ? "assistant" : "user",
               content: message.content,
             });
@@ -1278,27 +1107,23 @@ export const codeAgentFunction = inngest.createFunction(
       }
     });
 
-    const contextMessages: Message[] = (crawledContexts ?? []).map(
+    const contextMessages: CoreMessage[] = (crawledContexts ?? []).map(
       (context) => ({
-        type: "text",
-        role: "user",
+        role: "user" as const,
         content: `Crawled context from ${context.url}:\n${context.content}`,
       }),
     );
 
-    const initialMessages = [...contextMessages, ...previousMessages];
+    const initialMessages: CoreMessage[] = [...contextMessages, ...previousMessages];
 
-    const state = createState<AgentState>(
-      {
-        summary: "",
-        files: {},
-        selectedFramework,
-        summaryRetryCount: 0,
-      },
-      {
-        messages: initialMessages,
-      },
-    );
+    // Agent state for tracking files and summary
+    const agentState: AgentState = {
+      summary: "",
+      files: {},
+      selectedFramework,
+      summaryRetryCount: 0,
+    };
+    const stateRef = { current: agentState };
 
     // Check if this message has an approved spec
     const currentMessage = await step.run("get-current-message", async () => {
@@ -1355,101 +1180,116 @@ Generate code that matches the approved specification.`;
       }
     );
 
-    const codeAgent = createAgent<AgentState>({
-      name: `${selectedFramework}-code-agent`,
-      description: `An expert ${selectedFramework} coding agent powered by ${modelConfig.name}`,
-      system: frameworkPrompt,
-      model: getModelAdapter(selectedModel, modelConfig.temperature),
-      tools: createCodeAgentTools(sandboxId),
-      lifecycle: {
-        onResponse: async ({ result, network }) => {
-          const lastAssistantMessageText =
-            lastAssistantTextMessageContent(result);
-
-          if (lastAssistantMessageText && network) {
-            const containsSummaryTag =
-              lastAssistantMessageText.includes("<task_summary>");
-            console.log(
-              `[DEBUG] Agent response received (contains summary tag: ${containsSummaryTag})`,
-            );
-            if (containsSummaryTag) {
-              network.state.data.summary = extractSummaryText(
-                lastAssistantMessageText,
-              );
-              network.state.data.summaryRetryCount = 0;
-            }
-          }
+    // Create AI SDK tools for the sandbox
+    const tools = createAISDKTools(sandboxId, stateRef);
+    const MAX_ITERATIONS = 8;
 
-          return result;
-        },
-      },
-    });
+    // Build conversation messages
+    let messages: CoreMessage[] = [
+      ...initialMessages,
+      { role: "user", content: event.data.value },
+    ];
 
-    const network = createNetwork<AgentState>({
-      name: "coding-agent-network",
-      agents: [codeAgent],
-      maxIter: 8,
-      defaultState: state,
-      router: async ({ network }) => {
-        const summaryText = extractSummaryText(
-          network.state.data.summary ?? "",
-        );
-        const fileEntries = network.state.data.files ?? {};
-        const fileCount = Object.keys(fileEntries).length;
+    console.log("[DEBUG] Running AI SDK agent with input:", event.data.value);
+    
+    // Main agent loop using AI SDK
+    let iteration = 0;
+    let lastOutput = "";
+    
+    while (iteration < MAX_ITERATIONS) {
+      iteration++;
+      console.log(`[AI-SDK] Running iteration ${iteration}/${MAX_ITERATIONS}`);
 
-        if (summaryText.length > 0) {
-          return;
-        }
+      try {
+        const result = await generateText({
+          model: gateway(selectedModel),
+          system: frameworkPrompt,
+          messages,
+          tools,
+          maxSteps: 10,
+          temperature: modelConfig.temperature,
+          onStepFinish: async ({ text }) => {
+            if (text) {
+              lastOutput = text;
+              const summary = extractSummaryText(text);
+              if (summary) {
+                stateRef.current.summary = summary;
+                stateRef.current.summaryRetryCount = 0;
+              }
+            }
+          },
+        });
+
+        if (result.text) {
+          lastOutput = result.text;
+          messages.push({ role: "assistant", content: result.text });
 
-        if (fileCount === 0) {
-          network.state.data.summaryRetryCount = 0;
-          return codeAgent;
+          const summary = extractSummaryText(result.text);
+          if (summary) {
+            stateRef.current.summary = summary;
+            console.log(`[AI-SDK] Summary extracted (length: ${summary.length})`);
+            break;
+          }
         }
 
-        const currentRetry = network.state.data.summaryRetryCount ?? 0;
-        if (currentRetry >= 2) {
-          console.warn(
-            "[WARN] Missing <task_summary> after multiple attempts despite generated files; proceeding with fallback handling.",
-          );
-          return;
+        const hasFiles = Object.keys(stateRef.current.files).length > 0;
+        const hasSummary = stateRef.current.summary.length > 0;
+
+        if (hasSummary) {
+          console.log("[AI-SDK] Task complete with summary");
+          break;
         }
 
-        const nextRetry = currentRetry + 1;
-        network.state.data.summaryRetryCount = nextRetry;
-        console.log(
-          `[DEBUG] No <task_summary> yet; retrying agent to request summary (attempt ${nextRetry}).`,
-        );
-        
-        return codeAgent;
-      },
-    });
+        if (hasFiles && !hasSummary) {
+          stateRef.current.summaryRetryCount++;
+          if (stateRef.current.summaryRetryCount >= 2) {
+            console.warn("[AI-SDK] Missing summary after multiple attempts, proceeding");
+            break;
+          }
 
-    console.log("[DEBUG] Running network with input:", event.data.value);
-    let result = await network.run(event.data.value, { state });
+          messages.push({
+            role: "user",
+            content: "IMPORTANT: You have generated files but forgot to provide the <task_summary> tag. Please provide it now with a brief description of what you built.",
+          });
+        }
+      } catch (error) {
+        console.error(`[AI-SDK] Error in iteration ${iteration}:`, error);
+        throw error;
+      }
+    }
 
     // Post-network fallback: If no summary but files exist, make one more explicit request
-    let summaryText = extractSummaryText(result.state.data.summary ?? "");
-    const hasGeneratedFiles = Object.keys(result.state.data.files || {}).length > 0;
+    let summaryText = extractSummaryText(stateRef.current.summary ?? "");
+    const hasGeneratedFiles = Object.keys(stateRef.current.files || {}).length > 0;
     
     if (!summaryText && hasGeneratedFiles) {
-      console.log("[DEBUG] No summary detected after network run, requesting explicitly...");
-      result = await network.run(
-        "IMPORTANT: You have successfully generated files, but you forgot to provide the <task_summary> tag. Please provide it now with a brief description of what you built. This is required to complete the task.",
-        { state: result.state }
-      );
+      console.log("[DEBUG] No summary detected after agent run, requesting explicitly...");
       
-      // Re-extract summary after explicit request
-      summaryText = extractSummaryText(result.state.data.summary ?? "");
+      messages.push({
+        role: "user",
+        content: "IMPORTANT: You have successfully generated files, but you forgot to provide the <task_summary> tag. Please provide it now with a brief description of what you built. This is required to complete the task.",
+      });
+
+      const summaryResult = await generateText({
+        model: gateway(selectedModel),
+        system: frameworkPrompt,
+        messages,
+        temperature: modelConfig.temperature,
+      });
       
-      if (summaryText) {
-        console.log("[DEBUG] Summary successfully extracted after explicit request");
-      } else {
-        console.warn("[WARN] Summary still missing after explicit request, will use fallback");
+      if (summaryResult.text) {
+        summaryText = extractSummaryText(summaryResult.text);
+        if (summaryText) {
+          stateRef.current.summary = summaryText;
+          console.log("[DEBUG] Summary successfully extracted after explicit request");
+        } else {
+          console.warn("[WARN] Summary still missing after explicit request, will use fallback");
+        }
       }
     }
 
     // Post-execution validation: Check if expected entry point file was modified
-    const generatedFiles = result.state.data.files || {};
+    const generatedFiles = stateRef.current.files || {};
     const fileKeys = Object.keys(generatedFiles);
     
     // Define expected entry points by framework
@@ -1509,10 +1349,10 @@ Generate code that matches the approved specification.`;
     ]);
 
     let autoFixAttempts = 0;
-    let lastAssistantMessage = getLastAssistantMessage(result);
+    let lastAssistantMessage = getLastAssistantMessage(messages);
 
     if (selectedFramework === "nextjs") {
-      const currentFiles = (result.state.data.files || {}) as Record<
+      const currentFiles = (stateRef.current.files || {}) as Record<
         string,
         string
       >;
@@ -1564,8 +1404,7 @@ Generate code that matches the approved specification.`;
         `\n[DEBUG] Auto-fix triggered (attempt ${autoFixAttempts}). Errors detected.\n${errorDetails}\n`,
       );
 
-      result = await network.run(
-        `CRITICAL ERROR DETECTED - IMMEDIATE FIX REQUIRED
+      const autoFixPrompt = `CRITICAL ERROR DETECTED - IMMEDIATE FIX REQUIRED
 
 The previous attempt encountered an error that must be corrected before proceeding.
 
@@ -1587,11 +1426,32 @@ REQUIRED ACTIONS:
 6. Rerun any commands that failed and verify they now succeed
 7. Provide an updated <task_summary> only after the error is fully resolved
 
-DO NOT proceed until the error is completely fixed. The fix must be thorough and address the root cause, not just mask the symptoms.`,
-        { state: result.state },
-      );
+DO NOT proceed until the error is completely fixed. The fix must be thorough and address the root cause, not just mask the symptoms.`;
+
+      messages.push({ role: "user", content: autoFixPrompt });
 
-      lastAssistantMessage = getLastAssistantMessage(result);
+      const fixResult = await generateText({
+        model: gateway(selectedModel),
+        system: frameworkPrompt,
+        messages,
+        tools,
+        maxSteps: 10,
+        temperature: modelConfig.temperature,
+        onStepFinish: async ({ text }) => {
+          if (text) {
+            const summary = extractSummaryText(text);
+            if (summary) {
+              stateRef.current.summary = summary;
+            }
+          }
+        },
+      });
+
+      if (fixResult.text) {
+        messages.push({ role: "assistant", content: fixResult.text });
+      }
+
+      lastAssistantMessage = getLastAssistantMessage(messages);
 
       // Re-run validation checks to verify if errors are actually fixed
       console.log(
@@ -1629,15 +1489,15 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
       }
     }
 
-    lastAssistantMessage = getLastAssistantMessage(result);
+    lastAssistantMessage = getLastAssistantMessage(messages);
 
-    const files = (result.state.data.files || {}) as Record<string, string>;
+    const files = (stateRef.current.files || {}) as Record<string, string>;
     const filePaths = Object.keys(files);
     const hasFiles = filePaths.length > 0;
 
     summaryText = extractSummaryText(
-      typeof result.state.data.summary === "string"
-        ? result.state.data.summary
+      typeof stateRef.current.summary === "string"
+        ? stateRef.current.summary
         : "",
     );
     const agentProvidedSummary = summaryText.length > 0;
@@ -1652,7 +1512,7 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
       );
     }
 
-    result.state.data.summary = summaryText;
+    stateRef.current.summary = summaryText;
 
     const hasSummary = summaryText.length > 0;
 
@@ -1722,34 +1582,28 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
       return fallbackHost;
     });
 
-    let fragmentTitleOutput: Message[] | undefined;
-    let responseOutput: Message[] | undefined;
+    let fragmentTitleOutput: string | undefined;
+    let responseOutput: string | undefined;
 
     if (!isError && hasSummary && hasFiles) {
       try {
-        const titleModel = getModelAdapter("google/gemini-2.5-flash-lite", 0.3);
-
-        const fragmentTitleGenerator = createAgent({
-          name: "fragment-title-generator",
-          description: "A fragment title generator",
-          system: FRAGMENT_TITLE_PROMPT,
-          model: titleModel,
-        });
-
-        const responseGenerator = createAgent({
-          name: "response-generator",
-          description: "A response generator",
-          system: RESPONSE_PROMPT,
-          model: titleModel,
-        });
-
         const [titleResult, responseResult] = await Promise.all([
-          fragmentTitleGenerator.run(summaryText),
-          responseGenerator.run(summaryText),
+          generateText({
+            model: gateway("google/gemini-2.5-flash-lite"),
+            system: FRAGMENT_TITLE_PROMPT,
+            messages: [{ role: "user", content: summaryText }],
+            temperature: 0.3,
+          }),
+          generateText({
+            model: gateway("google/gemini-2.5-flash-lite"),
+            system: RESPONSE_PROMPT,
+            messages: [{ role: "user", content: summaryText }],
+            temperature: 0.3,
+          }),
         ]);
 
-        fragmentTitleOutput = titleResult.output;
-        responseOutput = responseResult.output;
+        fragmentTitleOutput = titleResult.text;
+        responseOutput = responseResult.text;
       } catch (gatewayError) {
         console.error(
           "[ERROR] Failed to generate fragment metadata:",
@@ -2008,10 +1862,7 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
         });
       }
 
-      const parsedResponse = parseAgentOutput(responseOutput);
-      const parsedTitle = parseAgentOutput(fragmentTitleOutput);
-
-      const sanitizedResponse = sanitizeTextForDatabase(parsedResponse ?? "");
+      const sanitizedResponse = sanitizeTextForDatabase(responseOutput ?? "");
       const baseResponseContent =
         sanitizedResponse.length > 0
           ? sanitizedResponse
@@ -2026,7 +1877,7 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
         `${baseResponseContent}${warningsNote}`,
       );
 
-      const sanitizedTitle = sanitizeTextForDatabase(parsedTitle ?? "");
+      const sanitizedTitle = sanitizeTextForDatabase(fragmentTitleOutput ?? "");
       const fragmentTitle =
         sanitizedTitle.length > 0 ? sanitizedTitle : "Generated Fragment";
 
@@ -2074,7 +1925,7 @@ DO NOT proceed until the error is completely fixed. The fix must be thorough and
       url: sandboxUrl,
       title: "Fragment",
       files: finalFiles,
-      summary: result.state.data.summary,
+      summary: stateRef.current.summary,
     };
   },
 );
@@ -2286,20 +2137,16 @@ export const errorFixFunction = inngest.createFunction(
 
     console.log("[DEBUG] Errors detected, running fix agent...");
 
-    // Create a minimal state with existing files
-    const state = createState<AgentState>(
-      {
-        summary:
-          ((fragmentRecord.metadata as Record<string, unknown>)
-            ?.summary as string) ?? "",
-        files: fragmentFiles,
-        selectedFramework: fragmentFramework,
-        summaryRetryCount: 0,
-      },
-      {
-        messages: [],
-      },
-    );
+    // Create agent state with existing files
+    const errorFixState: AgentState = {
+      summary:
+        ((fragmentRecord.metadata as Record<string, unknown>)
+          ?.summary as string) ?? "",
+      files: fragmentFiles,
+      selectedFramework: fragmentFramework,
+      summaryRetryCount: 0,
+    };
+    const errorFixStateRef = { current: errorFixState };
 
     const frameworkPrompt = getFrameworkPrompt(fragmentFramework);
     const errorFixModelConfig = MODEL_CONFIGS[fragmentModel];
@@ -2310,80 +2157,8 @@ export const errorFixFunction = inngest.createFunction(
       errorFixModelConfig,
     );
 
-    const codeAgent = createAgent<AgentState>({
-      name: `${fragmentFramework}-error-fix-agent`,
-      description: `An expert ${fragmentFramework} coding agent for fixing errors powered by ${errorFixModelConfig.name}`,
-      system: frameworkPrompt,
-      model: openai({
-        model: fragmentModel,
-        apiKey: process.env.AI_GATEWAY_API_KEY!,
-        baseUrl:
-          process.env.AI_GATEWAY_BASE_URL || "https://ai-gateway.vercel.sh/v1",
-        defaultParameters: {
-          temperature: errorFixModelConfig.temperature,
-        },
-      }),
-      tools: createCodeAgentTools(sandboxId),
-      lifecycle: {
-        onResponse: async ({ result, network }) => {
-          const lastAssistantMessageText =
-            lastAssistantTextMessageContent(result);
-          if (lastAssistantMessageText && network) {
-            const containsSummaryTag =
-              lastAssistantMessageText.includes("<task_summary>");
-            console.log(
-              `[DEBUG] Error-fix agent response received (contains summary tag: ${containsSummaryTag})`,
-            );
-            if (containsSummaryTag) {
-              network.state.data.summary = extractSummaryText(
-                lastAssistantMessageText,
-              );
-              network.state.data.summaryRetryCount = 0;
-            }
-          }
-          return result;
-        },
-      },
-    });
-
-    const network = createNetwork<AgentState>({
-      name: "error-fix-network",
-      agents: [codeAgent],
-      maxIter: 10,
-      defaultState: state,
-      router: async ({ network }) => {
-        const summaryText = extractSummaryText(
-          network.state.data.summary ?? "",
-        );
-        const fileEntries = network.state.data.files ?? {};
-        const fileCount = Object.keys(fileEntries).length;
-
-        if (summaryText.length > 0) {
-          return;
-        }
-
-        if (fileCount === 0) {
-          network.state.data.summaryRetryCount = 0;
-          return codeAgent;
-        }
-
-        const currentRetry = network.state.data.summaryRetryCount ?? 0;
-        if (currentRetry >= 3) {
-          console.warn(
-            "[WARN] Error-fix agent missing <task_summary> after multiple retries; proceeding with collected fixes.",
-          );
-          return;
-        }
-
-        const nextRetry = currentRetry + 1;
-        network.state.data.summaryRetryCount = nextRetry;
-        console.log(
-          `[DEBUG] Error-fix agent missing <task_summary>; retrying (attempt ${nextRetry}).`,
-        );
-        
-        return codeAgent;
-      },
-    });
+    // Create AI SDK tools for the sandbox
+    const errorFixTools = createAISDKTools(sandboxId, errorFixStateRef);
 
     const fixPrompt = `CRITICAL ERROR FIX REQUEST
 
@@ -2406,26 +2181,63 @@ REQUIRED ACTIONS:
 DO NOT proceed until all errors are completely resolved. Focus on fixing the root cause, not just masking symptoms.`;
 
     try {
-      let result = await network.run(fixPrompt, { state });
+      // Run the error fix using AI SDK
+      const errorFixMessages: CoreMessage[] = [
+        { role: "user", content: fixPrompt },
+      ];
+
+      const fixResult = await generateText({
+        model: gateway(fragmentModel),
+        system: frameworkPrompt,
+        messages: errorFixMessages,
+        tools: errorFixTools,
+        maxSteps: 10,
+        temperature: errorFixModelConfig.temperature,
+        onStepFinish: async ({ text }) => {
+          if (text) {
+            const summary = extractSummaryText(text);
+            if (summary) {
+              errorFixStateRef.current.summary = summary;
+            }
+          }
+        },
+      });
 
-      // Post-network fallback: If no summary but files were modified, make one more explicit request
-      let summaryText = extractSummaryText(result.state.data.summary ?? "");
-      const hasModifiedFiles = Object.keys(result.state.data.files || {}).length > 0;
+      if (fixResult.text) {
+        errorFixMessages.push({ role: "assistant", content: fixResult.text });
+        const summary = extractSummaryText(fixResult.text);
+        if (summary) {
+          errorFixStateRef.current.summary = summary;
+        }
+      }
+
+      // Post-fix fallback: If no summary but files were modified, make one more explicit request
+      let summaryText = extractSummaryText(errorFixStateRef.current.summary ?? "");
+      const hasModifiedFiles = Object.keys(errorFixStateRef.current.files || {}).length > 0;
       
       if (!summaryText && hasModifiedFiles) {
         console.log("[DEBUG] No summary detected after error-fix, requesting explicitly...");
-        result = await network.run(
-          "IMPORTANT: You have successfully fixed the errors, but you forgot to provide the <task_summary> tag. Please provide it now with a brief description of what errors you fixed. This is required to complete the task.",
-          { state: result.state }
-        );
         
-        // Re-extract summary after explicit request
-        summaryText = extractSummaryText(result.state.data.summary ?? "");
+        errorFixMessages.push({
+          role: "user",
+          content: "IMPORTANT: You have successfully fixed the errors, but you forgot to provide the <task_summary> tag. Please provide it now with a brief description of what errors you fixed. This is required to complete the task.",
+        });
+
+        const summaryResult = await generateText({
+          model: gateway(fragmentModel),
+          system: frameworkPrompt,
+          messages: errorFixMessages,
+          temperature: errorFixModelConfig.temperature,
+        });
         
-        if (summaryText) {
-          console.log("[DEBUG] Summary successfully extracted after explicit request");
-        } else {
-          console.warn("[WARN] Summary still missing after explicit request, will use fallback");
+        if (summaryResult.text) {
+          summaryText = extractSummaryText(summaryResult.text);
+          if (summaryText) {
+            errorFixStateRef.current.summary = summaryText;
+            console.log("[DEBUG] Summary successfully extracted after explicit request");
+          } else {
+            console.warn("[WARN] Summary still missing after explicit request, will use fallback");
+          }
         }
       }
 
@@ -2455,7 +2267,7 @@ DO NOT proceed until all errors are completely resolved. Focus on fixing the roo
 
       // Ensure all fixed files are written back to the sandbox
       await step.run("sync-fixed-files-to-sandbox", async () => {
-        const fixedFiles = result.state.data.files || {};
+        const fixedFiles = errorFixStateRef.current.files || {};
         const sandbox = await getSandbox(sandboxId);
 
         console.log(
@@ -2520,7 +2332,7 @@ DO NOT proceed until all errors are completely resolved. Focus on fixing the roo
               previousFiles: originalFiles,
               fixedAt: new Date().toISOString(),
               lastFixSuccess: {
-                summary: result.state.data.summary,
+                summary: errorFixStateRef.current.summary,
                 occurredAt: new Date().toISOString(),
               },
             }
@@ -2532,7 +2344,7 @@ DO NOT proceed until all errors are completely resolved. Focus on fixing the roo
           sandboxId: fragment.sandboxId || undefined,
           sandboxUrl: fragment.sandboxUrl,
           title: fragment.title,
-          files: result.state.data.files,
+          files: errorFixStateRef.current.files,
           framework: frameworkToConvexEnum(fragmentFramework),
           metadata: metadataUpdate || fragment.metadata,
         });
@@ -2545,7 +2357,7 @@ DO NOT proceed until all errors are completely resolved. Focus on fixing the roo
         message: remainingErrors
           ? "Some errors may remain. Please check the sandbox."
           : "Errors fixed successfully",
-        summary: result.state.data.summary,
+        summary: errorFixStateRef.current.summary,
         remainingErrors: remainingErrors || undefined,
       };
     } catch (error) {
@@ -2630,32 +2442,16 @@ DO NOT proceed until all errors are completely resolved. Focus on fixing the roo
   },
 );
 
-// Helper function to extract spec content from agent response
-const extractSpecContent = (output: Message[]): string => {
-  const textContent = output
-    .filter((msg) => msg.type === "text")
-    .map((msg) => {
-      if (typeof msg.content === "string") {
-        return msg.content;
-      }
-      if (Array.isArray(msg.content)) {
-        return msg.content
-          .filter((c) => c.type === "text")
-          .map((c) => c.text)
-          .join("\n");
-      }
-      return "";
-    })
-    .join("\n");
-
+// Helper function to extract spec content from text response
+const extractSpecContent = (text: string): string => {
   // Extract content between <spec>...</spec> tags
-  const specMatch = /<spec>([\s\S]*?)<\/spec>/i.exec(textContent);
-  if (specMatch && specMatch[1]) {
+  const specMatch = /<spec>([\s\S]*?)<\/spec>/i.exec(text);
+  if (specMatch?.[1]) {
     return specMatch[1].trim();
   }
 
   // If no tags found, return the entire response
-  return textContent.trim();
+  return text.trim();
 };
 
 // Spec Planning Agent Function
@@ -2696,34 +2492,21 @@ export const specPlanningAgentFunction = inngest.createFunction(
     if (!project?.framework) {
       console.log("[DEBUG] No framework set, running framework selector...");
 
-      const frameworkSelectorAgent = createAgent({
-        name: "framework-selector",
-        description: "Determines the best framework for the user's request",
+      const frameworkResult = await generateText({
+        model: gateway("google/gemini-2.5-flash-lite"),
         system: FRAMEWORK_SELECTOR_PROMPT,
-        model: getModelAdapter("google/gemini-2.5-flash-lite", 0.3),
+        messages: [{ role: "user", content: event.data.value }],
+        temperature: 0.3,
       });
 
-      const frameworkResult = await frameworkSelectorAgent.run(
-        event.data.value,
-      );
-      const frameworkOutput = frameworkResult.output[0];
+      const detectedFramework = frameworkResult.text.trim().toLowerCase();
 
-      if (frameworkOutput.type === "text") {
-        const detectedFramework = (
-          typeof frameworkOutput.content === "string"
-            ? frameworkOutput.content
-            : frameworkOutput.content.map((c) => c.text).join("")
+      if (
+        ["nextjs", "angular", "react", "vue", "svelte"].includes(
+          detectedFramework,
         )
-          .trim()
-          .toLowerCase();
-
-        if (
-          ["nextjs", "angular", "react", "vue", "svelte"].includes(
-            detectedFramework,
-          )
-        ) {
-          selectedFramework = detectedFramework as Framework;
-        }
+      ) {
+        selectedFramework = detectedFramework as Framework;
       }
 
       // Update project with selected framework
@@ -2751,14 +2534,6 @@ ${frameworkPrompt}
 
 Remember to wrap your complete specification in <spec>...</spec> tags.`;
 
-    // Create planning agent with GPT-5.1 Codex
-    const planningAgent = createAgent({
-      name: "spec-planning-agent",
-      description: "Creates detailed implementation specifications",
-      system: enhancedSpecPrompt,
-      model: getModelAdapter("openai/gpt-5.1-codex", 0.7),
-    });
-
     console.log("[DEBUG] Running planning agent with user request");
 
     // Get previous messages for context
@@ -2774,40 +2549,32 @@ Remember to wrap your complete specification in <spec>...</spec> tags.`;
           // Take last 3 messages for context (excluding current one)
           const messages = allMessages.slice(-4, -1);
 
-          const formattedMessages: Message[] = messages.map((msg) => ({
-            type: "text",
+          const formattedMessages: CoreMessage[] = messages.map((msg) => ({
             role: msg.role === "ASSISTANT" ? "assistant" : "user",
             content: msg.content,
           }));
 
           return formattedMessages;
         } catch (error) {
           console.error("[ERROR] Failed to fetch previous messages:", error);
-          return [];
+          return [] as CoreMessage[];
         }
       },
     );
 
-    // Run the planning agent
+    // Run the planning agent using AI SDK
     const result = await step.run("generate-spec", async () => {
-      const state = createState<AgentState>(
-        {
-          summary: "",
-          files: {},
-          selectedFramework,
-          summaryRetryCount: 0,
-        },
-        {
-          messages: previousMessages,
-        },
-      );
-
-      const planResult = await planningAgent.run(event.data.value, { state });
+      const planResult = await generateText({
+        model: gateway("openai/gpt-5.1-codex"),
+        system: enhancedSpecPrompt,
+        messages: [...previousMessages, { role: "user", content: event.data.value }],
+        temperature: 0.7,
+      });
       return planResult;
     });
 
     // Extract spec content from response
-    const specContent = extractSpecContent(result.output);
+    const specContent = extractSpecContent(result.text);
 
     console.log("[DEBUG] Spec generated, length:", specContent.length);
 

File: src/inngest/types.ts
Changes:
@@ -2,14 +2,10 @@ export const SANDBOX_TIMEOUT = 30 * 60 * 1000; // 30 minutes in MS (reduced from
 
 export type Framework = 'nextjs' | 'angular' | 'react' | 'vue' | 'svelte';
 
-export interface AgentState {
-  summary: string;
-  files: Record<string, string>;
-  selectedFramework?: Framework;
-  summaryRetryCount: number;
-}
-
 export interface ClientState {
   projectId: string;
   userId?: string;
 }
+
+// Re-export AgentState from ai-sdk for backward compatibility
+export type { AgentState } from "@/ai-sdk/types";

File: src/inngest/utils.ts
Changes:
@@ -1,5 +1,4 @@
 import { Sandbox } from "@e2b/code-interpreter";
-import { AgentResult, Message, TextMessage } from "@inngest/agent-kit";
 
 import { SANDBOX_TIMEOUT } from "./types";
 
@@ -214,37 +213,3 @@ export async function readFilesFromSandbox(
     return [];
   }
 }
-
-export function lastAssistantTextMessageContent(result: AgentResult) {
-  const lastAssistantTextMessageIndex = result.output.findLastIndex(
-    (message) => message.role === "assistant",
-  );
-
-  const message = result.output[lastAssistantTextMessageIndex] as
-    | TextMessage
-    | undefined;
-
-  return message?.content
-    ? typeof message.content === "string"
-      ? message.content
-      : message.content.map((c) => c.text).join("")
-    : undefined;
-}
-
-export const parseAgentOutput = (value?: Message[]) => {
-  if (!value || value.length === 0) {
-    return "Fragment";
-  }
-
-  const output = value[0];
-
-  if (output.type !== "text") {
-    return "Fragment";
-  }
-
-  if (Array.isArray(output.content)) {
-    return output.content.map((txt) => (typeof txt === "string" ? txt : txt.text ?? "")).join("")
-  } else {
-    return output.content
-  }
-};

File: tests/mocks/ai-sdk-gateway.ts
Changes:
@@ -0,0 +1,6 @@
+export const createGateway = jest.fn(() => {
+  return jest.fn((modelId: string) => ({
+    modelId,
+    provider: "mock-gateway",
+  }));
+});

File: tests/mocks/ai.ts
Changes:
@@ -0,0 +1,17 @@
+export const generateText = jest.fn(async () => ({
+  text: "Mock AI response",
+  toolCalls: [],
+  toolResults: [],
+  finishReason: "stop",
+  usage: { promptTokens: 0, completionTokens: 0 },
+}));
+
+export const tool = jest.fn((config) => ({
+  ...config,
+  execute: config.execute,
+}));
+
+export type CoreMessage = {
+  role: "user" | "assistant" | "system";
+  content: string | Array<{ type: string; text?: string; image?: string }>;
+};

File: tests/mocks/inngest-agent-kit.ts
Changes:
@@ -1,18 +0,0 @@
-export const openai = () => ({
-  model: '',
-  apiKey: '',
-  baseUrl: '',
-  defaultParameters: {},
-});
-
-export const createAgent = () => ({
-  run: async () => ({ output: [] }),
-});
-
-export const createTool = () => ({ handler: async () => ({}) });
-export const createNetwork = () => ({ run: async () => ({ output: [] }) });
-export const createState = () => ({ get: () => ({}), set: () => undefined });
-
-export type Tool = Record<string, unknown>;
-export type Message = { type: string; content: unknown };
-export type NetworkRun = Record<string, unknown>;

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 29, 2025

Walkthrough

Migrates the codebase from Inngest's @inngest/agent-kit to the Vercel AI SDK (@ai-sdk/gateway and ai package). Introduces new AI SDK gateway configuration, tools, and type definitions, updates Inngest functions to use the new SDK, and replaces corresponding test mocks.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
jest.config.js, package.json
Updates test mock mappings to reference AI SDK mocks instead of agent-kit; removes @inngest/agent-kit and @inngest/realtime dependencies, adds ai ^4.3.16 and @ai-sdk/gateway ^0.0.9
New AI SDK Modules
src/ai-sdk/gateway.ts, src/ai-sdk/index.ts, src/ai-sdk/tools.ts, src/ai-sdk/types.ts
Introduces gateway factory with API key and base URL configuration, tool implementations for terminal, file operations, and sandbox interactions; exports public types and tools for agent state and context
Inngest Core Refactoring
src/inngest/functions.ts, src/inngest/types.ts, src/inngest/utils.ts
Rewrites agent functions to use AI SDK instead of agent-kit, replaces legacy Message types with CoreMessage, migrates state management to stateRef-based approach, removes old utility functions and re-exports AgentState from ai-sdk
Route & Endpoint
src/app/api/agent/token/route.ts
Updates comments to clarify realtime token handling via AI SDK streaming; endpoint remains placeholder
Test Mocks
tests/mocks/ai-sdk-gateway.ts, tests/mocks/ai.ts, tests/mocks/inngest-agent-kit.ts
Adds new mocks for AI SDK gateway and generateText/tool functions, removes all agent-kit mock implementations

Sequence Diagram

sequenceDiagram
    participant Inngest as Inngest Engine
    participant AgentFn as Agent Function
    participant AI as AI SDK (Gateway)
    participant Tools as Agent Tools
    participant Sandbox as Sandbox API
    participant State as stateRef

    Inngest->>AgentFn: Trigger codeAgentFunction
    AgentFn->>State: Initialize stateRef with AgentState
    AgentFn->>Tools: createCodeAgentTools(sandboxId, stateRef)
    Tools->>Tools: Setup terminal, createOrUpdateFiles, readFiles

    loop Agent Loop
        AgentFn->>AI: generateText with context + tools
        AI-->>AgentFn: Response (text/toolCalls)
        alt Tool Call Needed
            AgentFn->>Tools: Execute tool (terminal/file operation)
            Tools->>Sandbox: Send request (exec/read/write)
            Sandbox-->>Tools: Return output
            Tools->>State: Update stateRef.current
            Tools-->>AgentFn: Return ToolResult
        else No Tool Call
            AgentFn->>State: Extract summary from AI response
            AgentFn->>AgentFn: Update messages (CoreMessage[])
        end
    end

    AgentFn->>Sandbox: Finalize & sync files from stateRef.current.files
    AgentFn-->>Inngest: Return AgentResult with final state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • src/inngest/functions.ts: Extensive rewrite of agent execution logic, state management pattern changes, message type migration from legacy Message to CoreMessage
  • State management migration: Transition from result/network-based state to in-memory stateRef pattern with AgentState interface
  • Tool implementation changes: New sandbox-backed tools with zod validation and updated error handling
  • Integration points: AI SDK gateway integration, file path validation, size limit enforcement

Possibly related PRs

Suggested labels

capy

Poem

🐰 From agent-kit paths we hop away,
To Vercel's gateway, bright and new today!
Stateref tools dash through sandbox sand,
CoreMessages flow across the land—
AI SDK brings freedom, tools take flight! 🚀

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Migrate to AI SDK Gateway' clearly and concisely summarizes the primary change: replacing Inngest agent functionality with Vercel's AI SDK Gateway.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch tembo/remove-inngest-ai-gateway

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecapyai
Copy link

codecapyai bot commented Nov 29, 2025

🚀 Scrapybara Ubuntu instance started!

Interactive stream

⚠️ Error fetching GitHub variables, continuing setup:

status_code: 502, body: {'detail': "Error communicating with container: Client error '400 Bad Request' for url 'http://54.176.2.220:30038/env'\nFor more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/400"}

@codecapyai
Copy link

codecapyai bot commented Nov 29, 2025

🔧 Setting up test environment...

Agent Steps

@codecapyai
Copy link

codecapyai bot commented Nov 29, 2025

❌ Something went wrong:

status_code: 502, body: {'detail': {'error': "Unexpected error: Error code: 404 - {'type': 'error', 'error': {'type': 'not_found_error', 'message': 'model: claude-3-5-sonnet-20241022'}, 'request_id': 'req_011CVbt9Uk5DgUkZ1mvnC1xN'}", 'provider': 'anthropic', 'error_type': 'ProviderAPIError'}}

@claude
Copy link

claude bot commented Nov 29, 2025

PR Review: Migrate to AI SDK Gateway

I've completed a thorough review of this migration from Inngest Agent Kit to Vercel's AI SDK. Here's my feedback:


Strengths

1. Clean Architecture

  • Well-organized new `src/ai-sdk/` module with clear separation of concerns
  • Good use of TypeScript types throughout

2. Tool Implementation Quality

The new AI SDK tools in `src/ai-sdk/tools.ts` are well-structured:

  • Proper error handling with try-catch blocks
  • Consistent return types (`ToolResult`)
  • Clear Zod schemas for validation
  • Buffered stdout/stderr capture for terminal commands

3. Test Mocking Updated

  • Jest configuration properly updated
  • New mocks for `@ai-sdk/gateway` and `ai` packages
  • Removed obsolete Inngest agent kit mock

🔴 Critical Issues

1. Sandbox Connection Inefficiency (src/ai-sdk/tools.ts)

Problem: Each tool execution creates a new sandbox connection via `Sandbox.connect()`. With multiple tool calls per agent iteration, this creates unnecessary overhead.

Recommendation: Implement connection caching:
```typescript
const sandboxCache = new Map<string, Sandbox>();

async function getSandboxFromId(sandboxId: string): Promise {
if (sandboxCache.has(sandboxId)) {
return sandboxCache.get(sandboxId)!;
}
const sandbox = await Sandbox.connect(sandboxId, {
apiKey: process.env.E2B_API_KEY,
});
sandboxCache.set(sandboxId, sandbox);
return sandbox;
}
```

2. Missing Error Context in File Read (src/ai-sdk/tools.ts:110-115)

Problem: Swallowed exceptions provide no debugging information:

```typescript
} catch { // ❌ No error variable
contents.push({
path: filePath,
content: `[Error: Could not read file ${filePath}]`,
});
}
```

Fix:
```typescript
} catch (e) {
const errorMsg = e instanceof Error ? e.message : String(e);
contents.push({
path: filePath,
content: `[Error reading ${filePath}: ${errorMsg}]`,
});
}
```

3. State Mutation Without Validation (src/ai-sdk/tools.ts:75)

Problem: Direct mutation of `stateRef.current.files` without checks.

Recommendation: Add validation:
```typescript
if (!stateRef.current) {
throw new Error('State reference is invalid');
}
stateRef.current.files = updatedFiles;
```


⚠️ High Priority Issues

4. Incomplete Agent Loop Logic (src/inngest/functions.ts:1199-1240)

The new AI SDK loop has unclear termination conditions. What happens when no files are generated after N iterations?

Recommendation: Add explicit fallback:
```typescript
if (!hasFiles && iteration >= 3) {
console.warn('[AI-SDK] No files generated after 3 iterations');
break;
}
```

5. Removed Utility Functions Without Replacement (src/inngest/utils.ts)

The entire `utils.ts` file was deleted (35 lines). The new `getLastAssistantMessage` function reimplements message extraction but uses different logic.

Recommendation: Add unit tests to verify the new implementation matches expected behavior.

6. Message Format Changes May Break Previous Context (src/inngest/functions.ts:1021-1026)

Changed from Inngest-specific message format to AI SDK `CoreMessage`. If `previousMessages` include multi-modal content (images, etc.), they may not be properly formatted.


🟡 Medium Priority Issues

7. Inconsistent Error Handling Patterns

  • `tools.ts`: Uses `ToolResult` with `success/error` fields ✅
  • `functions.ts`: Uses try-catch with console.error ⚠️
  • `gateway.ts`: No error handling ❌

Recommendation: Establish consistent error handling strategy across the module.

8. Missing Input Validation (src/ai-sdk/gateway.ts)

No check if `AI_GATEWAY_API_KEY` is defined before using it.

Recommendation:
```typescript
const apiKey = process.env.AI_GATEWAY_API_KEY;
if (!apiKey) {
throw new Error('AI_GATEWAY_API_KEY environment variable is required');
}
```

9. Agent Token Route Comment Misleading (src/app/api/agent/token/route.ts:14)

Comment says "handled via AI SDK streaming" but the endpoint still returns 503.

Recommendation: Either implement the endpoint or clarify it's deprecated with a TODO/FIXME.


🟢 Minor Issues

10. Magic Numbers Without Constants

  • Line 1185: `MAX_ITERATIONS = 8` - Consider making this configurable
  • Line 1263: Retry limit of 2 hardcoded in multiple places

11. Console Logging Should Use Structured Logging

For production observability, consider structured logs instead of console.log.

12. Type Safety Could Be Stronger

Some places use `as const` for role assignment, others don't - be consistent.


📊 Performance Considerations

  1. Reduced Package Size ✅ - Removing `@inngest/agent-kit` and `@inngest/realtime` should reduce bundle size

  2. Sandbox Reconnection Overhead ⚠️ - Repeated `Sandbox.connect()` calls will add latency (Critical Issue 2 Sign in buttons #1)

  3. Message History Growth ⚠️ - The new loop appends to `messages` array on every iteration without size limits. Consider truncating old messages if token limits are exceeded.


🔒 Security Concerns

13. Environment Variable Exposure

`gateway.ts` uses env vars directly in module scope - ensure these aren't logged or exposed in error messages.

14. No Rate Limiting on Tool Execution

While E2B sandbox rate limiting exists (lines 852-884), individual tool executions aren't rate-limited. A malicious prompt could trigger rapid file operations.


Testing Gaps

  1. Missing Tests for New AI SDK Tools - No tests found for `createCodeAgentTools`, `getSandboxFromId`, tool execution paths

  2. Missing Integration Tests - Should test full agent flow with AI SDK (not just mocks)

  3. Migration Path Testing - Verify that old projects with Inngest-format messages still work


📝 Documentation Needs

  1. Update `CLAUDE.md` to reflect AI SDK Gateway instead of Inngest Agent Kit
  2. Add migration guide for existing deployments
  3. Document breaking changes (if any)

🎯 Overall Assessment

Verdict: ⚠️ Approve with Required Changes

This is a solid migration with good code structure, but the sandbox connection inefficiency (Critical Issue #1) and incomplete error handling need to be addressed before merging to production.

Before Merging:

  1. ✅ Implement sandbox connection caching
  2. ✅ Fix error handling in file read operations
  3. ✅ Add validation to environment variables in gateway.ts
  4. ✅ Clarify agent loop termination logic
  5. ⚠️ Add tests for new tools (can be follow-up PR)
  6. ⚠️ Update documentation (can be follow-up PR)

Estimated Risk:

  • Low if Critical Issues 2 Sign in buttons #1-3 are fixed
  • Medium if deployed as-is (performance degradation from sandbox reconnections)

Great work on this migration! The AI SDK is a cleaner abstraction than the previous Inngest Agent Kit. Once the critical issues are addressed, this will be a significant improvement. 🚀

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

https://github.com/otdoges/zapdev/blob/e9a605062d5543f3c4d0542c6878686aac800d83/src/inngest/functions.ts#L1756
P0 Badge Undefined agent result prevents fragment save

After migrating from Inngest agent-kit, the merge phase still reads result.state.data.files, but result is never defined in this function. When the code agent runs, this line will throw a ReferenceError before any files can be merged or saved, so the function will fail every time the merge step is reached regardless of inputs. The agent likely intended to use the AI SDK state (e.g., stateRef.current.files) instead.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Nov 29, 2025

Greptile Overview

Greptile Summary

Replaces Inngest Agent Kit with Vercel AI SDK for agent orchestration, moving from a network-based agent system to manual iteration loops with generateText() and custom tool execution.

Key Changes:

  • New src/ai-sdk/ module with gateway configuration, tool definitions, and type interfaces
  • Removed @inngest/agent-kit and @inngest/realtime dependencies, added @ai-sdk/gateway and ai
  • Replaced agent network (createAgent, createNetwork, createTool) with generateText() and manual while loop for iteration control
  • Tool execution changed from Inngest's step-based handlers to AI SDK's tool() function with direct async execution
  • Message format converted from Inngest's Message type to AI SDK's CoreMessage type
  • Agent state management moved from createState() to manual stateRef object mutation
  • Updated test mocks to reflect new SDK imports

Impact:

  • Architecture remains functionally equivalent with same tool capabilities (terminal, file operations)
  • Manual iteration control provides more transparency but requires explicit loop management
  • Gateway abstraction enables multi-provider support through Vercel's AI Gateway

Confidence Score: 4/5

  • This PR is safe to merge with one minor URL inconsistency to fix
  • The migration is well-executed with clean separation of concerns in the new ai-sdk module, proper error handling, and maintained functionality. One logical issue exists with inconsistent gateway URL configuration. The manual iteration approach is more verbose but provides better control. All tool implementations preserve error handling patterns.
  • Fix the URL inconsistency in src/ai-sdk/gateway.ts:4 before merging

Important Files Changed

File Analysis

Filename Score Overview
src/ai-sdk/gateway.ts 4/5 creates gateway configuration with minor URL inconsistency
src/ai-sdk/tools.ts 5/5 implements three E2B sandbox tools with proper error handling
src/inngest/functions.ts 4/5 migrates agent orchestration from Inngest Agent Kit to AI SDK with generateText and manual iteration loop
package.json 5/5 replaces @inngest/agent-kit and @inngest/realtime with @ai-sdk/gateway and ai

Sequence Diagram

sequenceDiagram
    participant User
    participant Inngest as Inngest Function
    participant Gateway as AI SDK Gateway
    participant AI as AI Model (via Gateway)
    participant Tools as AI SDK Tools
    participant Sandbox as E2B Sandbox

    User->>Inngest: code-agent/run event
    Inngest->>Inngest: Framework detection with generateText()
    Inngest->>Gateway: gateway(model)
    Gateway->>AI: HTTP request with auth
    AI-->>Gateway: Framework result
    Gateway-->>Inngest: Framework selected
    
    Inngest->>Inngest: Create agent state & tools
    Inngest->>Inngest: Build messages array
    
    loop Agent Iteration (max 8)
        Inngest->>Gateway: generateText(model, messages, tools)
        Gateway->>AI: Send messages + tool definitions
        
        alt Tool Call Requested
            AI-->>Gateway: Tool call response
            Gateway-->>Inngest: Tool execution needed
            Inngest->>Tools: Execute tool (terminal/files)
            Tools->>Sandbox: Connect & execute
            Sandbox-->>Tools: Result
            Tools-->>Inngest: ToolResult
            Inngest->>Inngest: Update state & messages
        else Text Response
            AI-->>Gateway: Text completion
            Gateway-->>Inngest: result.text
            Inngest->>Inngest: Extract summary if present
        end
        
        alt Summary Found
            Inngest->>Inngest: Break loop
        else No Summary But Has Files
            Inngest->>Inngest: Retry prompt for summary
        end
    end
    
    Inngest->>Sandbox: Run lint & build checks
    Sandbox-->>Inngest: Validation results
    
    alt Errors Detected
        loop Auto-fix (max 2)
            Inngest->>Gateway: generateText with error context
            Gateway->>AI: Fix request
            AI-->>Gateway: Fixed code
            Gateway-->>Inngest: Fix result
            Inngest->>Tools: Update files
            Tools->>Sandbox: Write fixed files
            Inngest->>Sandbox: Re-validate
        end
    end
    
    Inngest->>Inngest: Save fragments to Convex
    Inngest-->>User: Completion
Loading

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

13 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/inngest/functions.ts (1)

1756-1781: Fix stale result.state.data.files reference in codeAgentFunction

Here:

const agentFiles = result.state.data.files || {};

result is not in scope at this point in codeAgentFunction (it only exists as a const inside the earlier while loop), and the AI SDK generateText result no longer exposes a state.data.files tree anyway. This will fail type-checking and at runtime.

You already have the agent’s file map tracked via stateRef.current.files and aliased as generatedFiles earlier in the function, so agentFiles should be derived from that instead. A minimal fix:

-    const agentFiles = result.state.data.files || {};
+    const agentFiles = generatedFiles;

This keeps the merge/validation logic unchanged while correctly using the new state source.

🧹 Nitpick comments (7)
src/app/api/agent/token/route.ts (1)

14-25: Documented placeholder is clear; consider Sentry for error tracking

The new comments correctly explain that realtime is now handled via AI SDK streaming and this endpoint is a stub. To align with the Sentry error-handling guideline, you could also capture exceptions in the catch block:

-import { getUser } from "@/lib/auth-server";
+import { getUser } from "@/lib/auth-server";
+import * as Sentry from "@sentry/nextjs";

  export async function POST() {
    try {
      const user = await getUser();
      // ...
    } catch (error) {
-    console.error("[ERROR] Failed to generate realtime token:", error);
+    Sentry.captureException(error);
+    console.error("[ERROR] Failed to generate realtime token:", error);
      return Response.json(
        { error: "Failed to generate token" },
        { status: 500 }
      );
    }
  }

As per coding guidelines, this keeps production errors visible without changing the current API surface.

src/inngest/types.ts (1)

10-11: AgentState re-export keeps inngest types backward compatible

Re-exporting AgentState from @/ai-sdk/types is a clean way to keep existing src/inngest consumers compiling after the migration.

One small follow-up you may consider later: if Framework is also defined in @/ai-sdk/types, re-exporting it here instead of maintaining a parallel union would avoid type drift between the two modules.

src/ai-sdk/gateway.ts (1)

1-8: Avoid sending Bearer undefined when AI_GATEWAY_API_KEY is missing

If AI_GATEWAY_API_KEY is not set, this code will still send an Authorization: Bearer undefined header, which obscures misconfiguration and produces less helpful failures.

Consider guarding the header on presence of the key (and optionally logging/throwing early):

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}`,
-  },
+  headers: process.env.AI_GATEWAY_API_KEY
+    ? { Authorization: `Bearer ${process.env.AI_GATEWAY_API_KEY}` }
+    : {},
});

This aligns with the env-variable expectations in AGENTS.md while failing fast in a clearer way when configuration is incomplete.

src/ai-sdk/tools.ts (1)

52-92: Validate and sanitize file paths before sandbox read/write

createOrUpdateFilesTool and readFilesTool accept arbitrary paths and pass them directly to sandbox.files.write/sandbox.files.read. Elsewhere (e.g., isValidFilePath and readFileWithTimeout in src/inngest/functions.ts), you already enforce strict path validation to prevent traversal and keep everything under known workspace roots.

To keep behavior consistent and align with the repo’s “sanitize file paths to prevent directory traversal attacks” guideline, consider:

  • Reusing a shared isValidFilePath-style helper (moved to a small shared module) in these tools, and
  • Skipping or explicitly erroring on invalid paths rather than blindly attempting the operation.

This keeps the agent from accidentally touching unexpected locations even inside the sandbox and makes file handling semantics uniform across the codebase.

Also applies to: 94-129

src/ai-sdk/types.ts (1)

3-28: Avoid duplicating the Framework union type

This file defines Framework while src/inngest/types.ts already exports an identical union. Keeping two independent definitions risks subtle drift if a new framework is added or one side is updated.

Consider centralizing Framework in a small shared types module (e.g., src/shared/framework-types.ts) and re-exporting it from both src/ai-sdk/types.ts and src/inngest/types.ts so there’s a single source of truth.

src/inngest/functions.ts (2)

291-309: Make getLastAssistantMessage resilient to non-text content variants

getLastAssistantMessage assumes that any non-string msg.content is an array of objects with { type: "text"; text: string } and blindly casts each part. If the underlying CoreMessage type gains new shapes (e.g., tool calls, images, or other non-text parts without a text field), this cast could throw at runtime.

You can make this more defensive by checking the shape before access, e.g.:

if (Array.isArray(msg.content)) {
  return msg.content
    .filter(
      (part): part is { type: "text"; text: string } =>
        part.type === "text" && typeof (part as any).text === "string",
    )
    .map((part) => part.text)
    .join("");
}

This keeps the helper robust to future CoreMessage evolutions while still extracting the text you care about.


2141-2213: Harden model lookup in errorFixFunction to handle legacy/unknown values

In errorFixFunction, fragmentModel is derived from initialMetadata.model and cast to keyof typeof MODEL_CONFIGS, then used for both MODEL_CONFIGS[fragmentModel] and gateway(fragmentModel):

const fragmentModel =
  (initialMetadata.model as keyof typeof MODEL_CONFIGS) ||
  "anthropic/claude-haiku-4.5";

const errorFixModelConfig = MODEL_CONFIGS[fragmentModel];

If any existing fragment has a model value not present in MODEL_CONFIGS (e.g., older data written before this enum was introduced), errorFixModelConfig will be undefined and accessing errorFixModelConfig.temperature will throw.

Consider:

  • Validating fragmentModel against MODEL_CONFIGS and falling back when missing, e.g.:
const defaultModel: keyof typeof MODEL_CONFIGS = "anthropic/claude-haiku-4.5";
const isKnownModel = fragmentModel in MODEL_CONFIGS;
const effectiveModel = (isKnownModel ? fragmentModel : defaultModel) as keyof typeof MODEL_CONFIGS;
const errorFixModelConfig = MODEL_CONFIGS[effectiveModel];
  • Using effectiveModel consistently for both gateway(effectiveModel) and metadata.

This keeps error-fix robust even if older fragments carry unexpected model strings.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 44eef80 and e9a6050.

📒 Files selected for processing (13)
  • jest.config.js (1 hunks)
  • package.json (1 hunks)
  • src/ai-sdk/gateway.ts (1 hunks)
  • src/ai-sdk/index.ts (1 hunks)
  • src/ai-sdk/tools.ts (1 hunks)
  • src/ai-sdk/types.ts (1 hunks)
  • src/app/api/agent/token/route.ts (1 hunks)
  • src/inngest/functions.ts (27 hunks)
  • src/inngest/types.ts (1 hunks)
  • src/inngest/utils.ts (0 hunks)
  • tests/mocks/ai-sdk-gateway.ts (1 hunks)
  • tests/mocks/ai.ts (1 hunks)
  • tests/mocks/inngest-agent-kit.ts (0 hunks)
💤 Files with no reviewable changes (2)
  • src/inngest/utils.ts
  • tests/mocks/inngest-agent-kit.ts
🧰 Additional context used
📓 Path-based instructions (12)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/rules.mdc)

**/*.{ts,tsx}: Use Strict TypeScript - avoid any types
Use proper error handling with Sentry integration

**/*.{ts,tsx}: Avoid any type in TypeScript - use proper typing or unknown for uncertain types
Define interfaces/types for all data structures in TypeScript
Use Sentry to capture exceptions in production with Sentry.captureException() and re-throw errors for proper handling
Sanitize file paths to prevent directory traversal attacks
Never expose secrets client-side; only use NEXT_PUBLIC_ prefix for public environment variables

**/*.{ts,tsx}: Use TypeScript strict mode with end-to-end type safety across frontend and backend
Prefer tRPC for type-safe API definitions between frontend and backend

**/*.{ts,tsx}: Use strict TypeScript without any types in AI agent code
Use modern framework patterns including Next.js App Router and React hooks
Implement accessibility and responsive design in UI components

Files:

  • src/ai-sdk/index.ts
  • src/app/api/agent/token/route.ts
  • tests/mocks/ai-sdk-gateway.ts
  • src/ai-sdk/gateway.ts
  • tests/mocks/ai.ts
  • src/ai-sdk/tools.ts
  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • src/inngest/functions.ts
src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/zapdev_rules.mdc)

src/**/*.{ts,tsx}: Use tRPC hooks for type-safe API calls with proper imports from @/trpc/client
Use functional components with TypeScript interfaces for props in React
Use React Query for server state management; use useState/useReducer for local state only
Always validate user inputs with Zod schemas

src/**/*.{ts,tsx}: Validate all user inputs using Zod schemas and sanitize file paths to prevent directory traversal attacks
Keep OAuth tokens encrypted in Convex and never expose API keys in client-side code (use NEXT_PUBLIC_ prefix only for public values)

Files:

  • src/ai-sdk/index.ts
  • src/app/api/agent/token/route.ts
  • src/ai-sdk/gateway.ts
  • src/ai-sdk/tools.ts
  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • src/inngest/functions.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always use bun for package management instead of npm or yarn

Files:

  • src/ai-sdk/index.ts
  • src/app/api/agent/token/route.ts
  • tests/mocks/ai-sdk-gateway.ts
  • src/ai-sdk/gateway.ts
  • tests/mocks/ai.ts
  • src/ai-sdk/tools.ts
  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • jest.config.js
  • src/inngest/functions.ts
**/*.{js,ts,tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Always use bun for installing packages and running scripts, never use npm or pnpm

Files:

  • src/ai-sdk/index.ts
  • src/app/api/agent/token/route.ts
  • tests/mocks/ai-sdk-gateway.ts
  • src/ai-sdk/gateway.ts
  • tests/mocks/ai.ts
  • src/ai-sdk/tools.ts
  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • jest.config.js
  • src/inngest/functions.ts
src/app/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/zapdev_rules.mdc)

Default to Server Components; only add 'use client' directive when needed for event handlers, browser APIs, React hooks, or third-party client libraries

Implement Next.js App Router with server components by default, using client components only when necessary for interactivity

Files:

  • src/app/api/agent/token/route.ts
src/app/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use tRPC procedures for API routes instead of raw Next.js API routes

Files:

  • src/app/api/agent/token/route.ts
tests/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

tests/**/*.{ts,tsx}: Include security, sanitization, and file operation tests in all Jest test files
Use Jest test patterns matching **/__tests__/**/*.ts or **/?(*.)+(spec|test).ts with coverage scope src/**/*.ts

Files:

  • tests/mocks/ai-sdk-gateway.ts
  • tests/mocks/ai.ts
src/inngest/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Implement auto-fix retry logic for code generation with max 2 retry attempts on lint/build errors, running bun run lint && bun run build for validation

Files:

  • src/inngest/types.ts
  • src/inngest/functions.ts
src/inngest/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

src/inngest/**/*.{ts,tsx}: Never start dev servers in E2B sandboxes during code generation
Always run bun run lint and bun run build for validation after code generation in sandboxes

Files:

  • src/inngest/types.ts
  • src/inngest/functions.ts
package.json

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

Add @types/node to package.json when using any Node.js built-in modules

Files:

  • package.json
{package.json,package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lockb}

📄 CodeRabbit inference engine (.cursor/rules/rules.mdc)

Always use bun for package management - never npm, yarn, or pnpm

Files:

  • package.json
src/inngest/functions.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Update E2B template names in src/inngest/functions.ts around line 22 after building new sandbox templates

Files:

  • src/inngest/functions.ts
🧠 Learnings (21)
📓 Common learnings
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to sandbox-templates/** : Build E2B templates with Docker before running AI code generation, and update template name in src/inngest/functions.ts after building
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Set required environment variables: NEXT_PUBLIC_CONVEX_URL, AI_GATEWAY_API_KEY, AI_GATEWAY_BASE_URL, E2B_API_KEY, SCRAPYBARA_API_KEY, NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY, INNGEST_EVENT_KEY, INNGEST_SIGNING_KEY

Applied to files:

  • src/ai-sdk/gateway.ts
  • package.json
  • src/inngest/functions.ts
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to src/prompts/*.ts : Create framework-specific AI prompts for each supported framework (nextjs, angular, react, vue, svelte) in the prompts directory

Applied to files:

  • tests/mocks/ai.ts
  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • src/inngest/functions.ts
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to sandbox-templates/** : Build E2B templates with Docker before running AI code generation, and update template name in src/inngest/functions.ts after building

Applied to files:

  • src/ai-sdk/tools.ts
  • jest.config.js
  • src/inngest/functions.ts
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to **/*.{ts,tsx} : Use strict TypeScript without `any` types in AI agent code

Applied to files:

  • src/ai-sdk/types.ts
  • src/inngest/types.ts
  • jest.config.js
📚 Learning: 2025-11-28T02:59:13.470Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/zapdev_rules.mdc:0-0
Timestamp: 2025-11-28T02:59:13.470Z
Learning: Applies to **/*.{ts,tsx} : Define interfaces/types for all data structures in TypeScript

Applied to files:

  • src/ai-sdk/types.ts
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to **/*.{ts,tsx} : Prefer tRPC for type-safe API definitions between frontend and backend

Applied to files:

  • src/ai-sdk/types.ts
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to **/*.{ts,tsx} : Use TypeScript strict mode with end-to-end type safety across frontend and backend

Applied to files:

  • src/ai-sdk/types.ts
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to src/prompts/**/*.ts : Implement framework detection logic following priority: explicit user mention → Next.js default → enterprise indicators → Material Design preference → performance critical

Applied to files:

  • src/ai-sdk/types.ts
  • src/inngest/functions.ts
📚 Learning: 2025-11-28T02:59:13.470Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/zapdev_rules.mdc:0-0
Timestamp: 2025-11-28T02:59:13.470Z
Learning: Applies to **/?(*.)+(spec|test).{ts,tsx} : Place test files in `/tests/` directory or use patterns `**/__tests__/**/*.ts` or `**/?(*.)+(spec|test).ts`

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to tests/**/*.{ts,tsx} : Use Jest test patterns matching `**/__tests__/**/*.ts` or `**/?(*.)+(spec|test).ts` with coverage scope `src/**/*.ts`

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-28T02:58:53.068Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-28T02:58:53.068Z
Learning: Applies to convex/**/*.{ts,tsx} : Convex uses file-based routing; a public function `f` in `convex/example.ts` has function reference `api.example.f`

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-28T02:58:53.068Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-28T02:58:53.068Z
Learning: Applies to convex/**/*.{ts,tsx} : Thoughtfully organize files with public query, mutation, or action functions within the `convex/` directory following file-based routing

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to src/inngest/**/*.{ts,tsx} : Never start dev servers in E2B sandboxes during code generation

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: Applies to src/inngest/**/*.{ts,tsx} : Always run `bun run lint` and `bun run build` for validation after code generation in sandboxes

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to tests/**/*.{ts,tsx} : Include security, sanitization, and file operation tests in all Jest test files

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-28T02:58:53.068Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-28T02:58:53.068Z
Learning: Applies to convex/**/*.{ts,tsx} : Private functions follow file-based routing; a private function `g` in `convex/example.ts` has function reference `internal.example.g`

Applied to files:

  • jest.config.js
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Applies to src/inngest/functions.ts : Update E2B template names in src/inngest/functions.ts around line 22 after building new sandbox templates

Applied to files:

  • src/inngest/functions.ts
📚 Learning: 2025-11-28T02:58:53.068Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: .cursor/rules/convex_rules.mdc:0-0
Timestamp: 2025-11-28T02:58:53.068Z
Learning: Applies to convex/**/*.{ts,tsx} : Use the `api` object from `convex/_generated/api.ts` to call public functions registered with `query`, `mutation`, or `action`

Applied to files:

  • src/inngest/functions.ts
📚 Learning: 2025-11-29T05:36:45.260Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-29T05:36:45.260Z
Learning: AI code generation flow: user creates project → framework selector agent chooses framework → single code generation agent runs in E2B sandbox → automatic validation → save to Convex

Applied to files:

  • src/inngest/functions.ts
📚 Learning: 2025-11-29T05:36:32.157Z
Learnt from: CR
Repo: otdoges/zapdev PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-29T05:36:32.157Z
Learning: Implement single agent mode workflow: User Request → Framework Detection → Code Generation Agent (with createOrUpdateFiles, readFiles, terminal commands) → Save to Convex → Display Preview

Applied to files:

  • src/inngest/functions.ts
🧬 Code graph analysis (4)
src/ai-sdk/gateway.ts (2)
src/ai-sdk/index.ts (1)
  • gateway (1-1)
tests/mocks/ai-sdk-gateway.ts (1)
  • createGateway (1-6)
src/ai-sdk/tools.ts (2)
tests/mocks/e2b-code-interpreter.ts (1)
  • Sandbox (1-37)
src/ai-sdk/types.ts (2)
  • AgentState (5-10)
  • ToolResult (24-28)
src/ai-sdk/types.ts (2)
src/inngest/types.ts (1)
  • Framework (3-3)
tests/mocks/ai.ts (1)
  • CoreMessage (14-17)
src/inngest/functions.ts (6)
tests/mocks/ai.ts (1)
  • CoreMessage (14-17)
src/ai-sdk/index.ts (3)
  • gateway (1-1)
  • Framework (6-6)
  • AgentState (4-4)
src/prompt.ts (3)
  • FRAMEWORK_SELECTOR_PROMPT (7-7)
  • FRAGMENT_TITLE_PROMPT (1-1)
  • RESPONSE_PROMPT (1-1)
src/ai-sdk/types.ts (2)
  • Framework (3-3)
  • AgentState (5-10)
src/inngest/types.ts (2)
  • Framework (3-3)
  • AgentState (11-11)
src/prompts/shared.ts (2)
  • FRAGMENT_TITLE_PROMPT (311-320)
  • RESPONSE_PROMPT (302-309)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: claude-review
  • GitHub Check: Codacy Security Scan
🔇 Additional comments (7)
package.json (1)

15-19: AI SDK dependencies wired consistently

The ai and @ai-sdk/gateway dependencies align with the new src/ai-sdk/* modules and Jest mocks; no issues from a dependency wiring standpoint.

tests/mocks/ai-sdk-gateway.ts (1)

1-5: Gateway mock shape matches current usage

This mock mirrors how createGateway is used in src/ai-sdk/gateway.ts (called once with config and returning a callable gateway(modelId) function), while still allowing assertions on the initial config via createGateway.mock.calls. Looks good as-is.

src/ai-sdk/index.ts (1)

1-9: AI SDK index provides a clean public surface

Re-exporting gateway, createCodeAgentTools, and the core types from a single index keeps AI-related imports consistent and discoverable across the codebase.

jest.config.js (1)

14-15: Jest moduleNameMapper updated correctly for AI SDK mocks

The new mappings for ^@ai-sdk/gateway$ and ^ai$ correctly route AI-related imports to the mock implementations in tests/mocks, matching the dependency migration away from @inngest/agent-kit.

src/inngest/functions.ts (3)

1183-1263: Agent control flow, summary handling, and auto-fix orchestration look coherent

The refactored codeAgentFunction logic using stateRef looks well-structured:

  • The iterative loop with MAX_ITERATIONS, summaryRetryCount, and <task_summary> reminders provides a clear termination strategy and reduces chances of getting stuck without a summary.
  • Post-network fallback to explicitly request a summary when files exist but stateRef.current.summary is empty is a good safety net.
  • Validation + auto-fix flow (lint/build checks feeding into an error-focused prompt, plus capped AUTO_FIX_MAX_ATTEMPTS) aligns with the documented “auto-fix with up to 2 retries” behavior and keeps retries bounded.

Taken together, this should make downstream use of summary and generated files more reliable without risking unbounded loops.

Also applies to: 1392-1490


1865-1883: Response/fragment persistence wiring matches new state model

The updated “save-result” step now:

  • Uses the LLM-generated responseOutput and fragmentTitleOutput (with sanitization + sensible fallbacks),
  • Persists finalFiles (merged sandbox + agent files with validation and filtering), and
  • Returns summary: stateRef.current.summary in the function result.

This lines up with the new AgentState/stateRef flow and should keep the DB fragment, user-facing message, and returned payload in sync with what the agent actually produced.

Also applies to: 1924-1929


2446-2455: Spec extraction helper and spec-planning agent integration look correct

The new extractSpecContent helper and specPlanningAgentFunction wiring are coherent:

  • extractSpecContent prefers content inside <spec>...</spec> but gracefully falls back to the full trimmed response if tags are missing.
  • specPlanningAgentFunction:
    • Ensures framework is set (reusing the framework selector when needed),
    • Builds an enhanced spec prompt including framework-specific context,
    • Feeds recent conversation context as CoreMessage[], and
    • Stores the extracted spec with AWAITING_APPROVAL status.

This should give you a cleaner, more structured spec artifact without breaking existing approval flow.

Also applies to: 2458-2597

Comment on lines +1 to +17
export const generateText = jest.fn(async () => ({
text: "Mock AI response",
toolCalls: [],
toolResults: [],
finishReason: "stop",
usage: { promptTokens: 0, completionTokens: 0 },
}));

export const tool = jest.fn((config) => ({
...config,
execute: config.execute,
}));

export type CoreMessage = {
role: "user" | "assistant" | "system";
content: string | Array<{ type: string; text?: string; image?: string }>;
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Type config in tool mock to avoid implicit any under strict TS

With strict TypeScript, the config parameter in tool will be implicitly any, which conflicts with the repo guideline to avoid any types.

You can keep the mock flexible while satisfying strict typing by introducing a lightweight config type:

-export const tool = jest.fn((config) => ({
-  ...config,
-  execute: config.execute,
-}));
+type ToolConfig = {
+  execute: (args: unknown) => unknown | Promise<unknown>;
+  [key: string]: unknown;
+};
+
+export const tool = jest.fn((config: ToolConfig) => ({
+  ...config,
+  execute: config.execute,
+}));

This preserves the existing behavior in tests while keeping the mock compatible with strict TypeScript settings.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const generateText = jest.fn(async () => ({
text: "Mock AI response",
toolCalls: [],
toolResults: [],
finishReason: "stop",
usage: { promptTokens: 0, completionTokens: 0 },
}));
export const tool = jest.fn((config) => ({
...config,
execute: config.execute,
}));
export type CoreMessage = {
role: "user" | "assistant" | "system";
content: string | Array<{ type: string; text?: string; image?: string }>;
};
export const generateText = jest.fn(async () => ({
text: "Mock AI response",
toolCalls: [],
toolResults: [],
finishReason: "stop",
usage: { promptTokens: 0, completionTokens: 0 },
}));
type ToolConfig = {
execute: (args: unknown) => unknown | Promise<unknown>;
[key: string]: unknown;
};
export const tool = jest.fn((config: ToolConfig) => ({
...config,
execute: config.execute,
}));
export type CoreMessage = {
role: "user" | "assistant" | "system";
content: string | Array<{ type: string; text?: string; image?: string }>;
};
🤖 Prompt for AI Agents
In tests/mocks/ai.ts around lines 1 to 17, the tool mock's config parameter is
implicitly any under strict TS; add a lightweight TypeScript type for config
(e.g., an interface/type that at minimum declares execute: (...args: any[]) =>
any and optional other props) and annotate the tool signature as (config:
YourConfigType) => { ... } so the mock remains flexible but no longer uses
implicit any.

@Jackson57279 Jackson57279 deleted the tembo/remove-inngest-ai-gateway branch November 29, 2025 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant