diff --git a/CLAUDE.md b/CLAUDE.md index 19fa3569..ab8dfbd4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -59,6 +59,7 @@ npm run typecheck # Full TypeScript validation including example/convex - **Message-centric design** - All operations revolve around message persistence/retrieval - **Component-based encapsulation** - Uses Convex Components pattern - **Multi-user support** - Threads have optional `userId` for ownership +- **Tool approval flow** - Tools can require human approval via `needsApproval`, with `agent.approveToolCall()` and `agent.denyToolCall()` methods ### Database Tables (Convex Component) @@ -87,82 +88,138 @@ Follow the rules in `.cursor/rules/convex_rules.mdc`: - `@convex-dev/agent/validators` - Convex validators for integration - `@convex-dev/agent/test` - Testing utilities -## Upgrading to v0.6.0 (AI SDK v6) +## AI Guidance: Helping Users Upgrade to v0.6.0 -When helping users upgrade from @convex-dev/agent v0.3.x to v0.6.0, follow these steps: +**IMPORTANT: v0.6.0 requires AI SDK v6 (ai@^6.0.0)** -### Step 1: Update dependencies in package.json +When helping users upgrade from @convex-dev/agent v0.3.x (AI SDK v5) to v0.6.0 (AI SDK v6): + +### Step 1: Update Dependencies First + +Update all AI SDK packages together to avoid peer dependency conflicts: ```bash npm install @convex-dev/agent@^0.6.0 ai@^6.0.35 @ai-sdk/provider-utils@^4.0.6 +npm install @ai-sdk/openai@^3.0.10 # or whichever provider ``` -Also update any AI SDK provider packages: -```bash -npm install @ai-sdk/openai@^3.0.10 @ai-sdk/anthropic@^3.0.13 -``` +**Compatible sibling packages:** +- `@convex-dev/rag@^0.7.0` (v0.6.0 has type conflicts with AI SDK v6) +- `@convex-dev/workflow@^0.3.2` + +### Step 2: Detect v5 Patterns -### Step 2: Update tool definitions +Search for these patterns indicating v5 usage: +- `createTool({ args:` - should be `inputSchema` +- `createTool({ handler:` - should be `execute` +- `textEmbeddingModel:` - should be `embeddingModel` +- `maxSteps:` in generateText/streamText - should be `stopWhen: stepCountIs(N)` +- `mode: "json"` in generateObject - removed in v6 +- `@ai-sdk/*` packages at v1.x or v2.x - should be v3.x +- Type imports: `LanguageModelV2` → `LanguageModelV3`, `EmbeddingModel` → `EmbeddingModelV3` -Replace `parameters` with `inputSchema`: +### Step 3: Apply Transformations +**Tool definitions:** ```typescript -// Before (v5) +// BEFORE (v5) const myTool = createTool({ description: "...", parameters: z.object({ query: z.string() }), - execute: async (ctx, args) => { ... } + handler: async (ctx, args) => { + return args.query.toUpperCase(); + } }) -// After (v6) +// AFTER (v6) const myTool = createTool({ description: "...", inputSchema: z.object({ query: z.string() }), - execute: async (ctx, input, options) => { ... } + execute: async (ctx, input, options) => { + return input.query.toUpperCase(); + } }) ``` -### Step 3: Update maxSteps usage (if applicable) +**Agent embedding config:** +```typescript +// BEFORE +new Agent(components.agent, { + textEmbeddingModel: openai.embedding("text-embedding-3-small") +}) + +// AFTER +new Agent(components.agent, { + embeddingModel: openai.embedding("text-embedding-3-small") +}) +``` +**Step limits:** ```typescript -// Before (v5) +// BEFORE await agent.generateText(ctx, { threadId }, { prompt: "...", maxSteps: 5 }) -// After (v6) - maxSteps still works but stopWhen is preferred -import { stepCountIs } from "ai" +// AFTER +import { stepCountIs } from "@convex-dev/agent" await agent.generateText(ctx, { threadId }, { prompt: "...", stopWhen: stepCountIs(5) }) ``` -### Step 4: Update embedding model config (optional) +**Type imports:** +```typescript +// BEFORE (v5) +import type { LanguageModelV2 } from "@ai-sdk/provider"; +import type { EmbeddingModel } from "ai"; +let model: LanguageModelV2; +let embedder: EmbeddingModel; + +// AFTER (v6) +import type { LanguageModelV3, EmbeddingModelV3 } from "@ai-sdk/provider"; +let model: LanguageModelV3; +let embedder: EmbeddingModelV3; +``` +**generateObject (remove mode: "json"):** ```typescript -// Before -new Agent(components.agent, { - textEmbeddingModel: openai.embedding("text-embedding-3-small") +// BEFORE (v5) +await generateObject({ + model, + mode: "json", + schema: z.object({ ... }), + prompt: "..." }) -// After (textEmbeddingModel still works but embeddingModel is preferred) -new Agent(components.agent, { - embeddingModel: openai.embedding("text-embedding-3-small") +// AFTER (v6) - mode: "json" removed, just use schema +await generateObject({ + model, + schema: z.object({ ... }), + prompt: "..." }) ``` -### Step 5: Verify the upgrade +### Step 4: Verify ```bash npm run typecheck -npm run lint npm test ``` -### Common issues +### Common Issues + +- **EmbeddingModelV2 vs V3 errors**: Ensure all `@ai-sdk/*` packages are v3.x +- **Tool `args` vs `input`**: v6 uses `input` in execute signature (2nd param) +- **`mimeType` vs `mediaType`**: v6 prefers `mediaType` (backwards compat maintained) +- **Type import errors**: `LanguageModelV2` is now `LanguageModelV3`, `EmbeddingModel` is now `EmbeddingModelV3` (no longer generic) +- **generateObject mode errors**: `mode: "json"` was removed in v6 - just remove the mode option + +### New v6 Features to Mention -- **EmbeddingModelV2 vs V3 errors**: Ensure all @ai-sdk/* packages are updated to v3.x -- **Tool input/args**: v6 uses `input` instead of `args` in tool calls (backwards compat maintained) -- **mimeType vs mediaType**: v6 uses `mediaType` (backwards compat maintained) +After upgrade, users can now use: +- **Tool approval**: `needsApproval` in createTool, `agent.approveToolCall()`, `agent.denyToolCall()` +- **Reasoning streaming**: Works with models like Groq that support reasoning +- **Detailed token usage**: `inputTokenDetails`, `outputTokenDetails` in usage tracking diff --git a/MIGRATION.md b/MIGRATION.md index 6eff33f7..fdfc09bc 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -4,10 +4,14 @@ This guide helps you upgrade from @convex-dev/agent v0.3.x to v0.6.0. ## Step 1: Update dependencies +Update all AI SDK packages **together** to avoid peer dependency conflicts: + ```bash npm install @convex-dev/agent@^0.6.0 ai@^6.0.35 @ai-sdk/provider-utils@^4.0.6 ``` +### Official AI SDK providers + Update your AI SDK provider packages to v3.x: ```bash # For OpenAI @@ -18,8 +22,38 @@ npm install @ai-sdk/anthropic@^3.0.13 # For Groq npm install @ai-sdk/groq@^3.0.8 + +# For Google (Gemini) +npm install @ai-sdk/google@^3.0.8 +``` + +### Third-party providers + +Third-party providers also need updates to be compatible with AI SDK v6: + +```bash +# For OpenRouter +npm install @openrouter/ai-sdk-provider@^2.0.0 + +# For other providers, check their documentation for AI SDK v6 compatibility +``` + +### Handling dependency conflicts + +If you see peer dependency warnings or errors, try updating all packages at once: + +```bash +npm install @convex-dev/agent@^0.6.0 ai@^6.0.35 @ai-sdk/openai@^3.0.10 @openrouter/ai-sdk-provider@^2.0.0 ``` +If you still have conflicts, you can use `--force` as a last resort: + +```bash +npm install @convex-dev/agent@^0.6.0 --force +``` + +> **Note**: Using `--force` can lead to inconsistent dependency trees. After using it, verify your app works correctly and consider running `npm dedupe` to clean up. + ## Step 2: Update tool definitions Replace `parameters` with `inputSchema`: @@ -89,6 +123,30 @@ AI SDK v6 renamed `args` to `input` in tool calls. The library maintains backwar ### `mimeType` vs `mediaType` AI SDK v6 renamed `mimeType` to `mediaType`. Backwards compatibility is maintained. +### Peer dependency conflicts + +If you see errors like: +``` +npm error ERESOLVE unable to resolve dependency tree +npm error peer ai@"^5.0.0" from @openrouter/ai-sdk-provider@1.0.3 +``` + +This means a third-party provider needs updating. Common solutions: + +1. **Update the provider** to a version compatible with AI SDK v6 +2. **Check npm** for the latest version: `npm view @openrouter/ai-sdk-provider versions` +3. **Use `--force`** if a compatible version isn't available yet (temporary workaround) + +### Third-party provider compatibility + +| Provider | AI SDK v5 (ai@5.x) | AI SDK v6 (ai@6.x) | +|----------|-------------------|-------------------| +| @openrouter/ai-sdk-provider | v1.x | v2.x | +| @ai-sdk/openai | v1.x-v2.x | v3.x | +| @ai-sdk/anthropic | v1.x-v2.x | v3.x | +| @ai-sdk/groq | v1.x-v2.x | v3.x | +| @ai-sdk/google | v1.x-v2.x | v3.x | + ## More Information - [AI SDK v6 Migration Guide](https://ai-sdk.dev/docs/migration-guides/migration-guide-6-0) diff --git a/package.json b/package.json index f90ff564..9b63d397 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ }, "files": [ "dist", - "src" + "src", + "MIGRATION.md" ], "exports": { "./package.json": "./package.json", diff --git a/src/client/createTool.ts b/src/client/createTool.ts index eb42d15b..f21cf5c9 100644 --- a/src/client/createTool.ts +++ b/src/client/createTool.ts @@ -11,7 +11,7 @@ import type { GenericActionCtx, GenericDataModel } from "convex/server"; import type { ProviderOptions } from "../validators.js"; import type { Agent } from "./index.js"; -const MIGRATION_URL = "https://github.com/get-convex/agent/blob/main/MIGRATION.md"; +const MIGRATION_URL = "node_modules/@convex-dev/agent/MIGRATION.md"; const warnedDeprecations = new Set(); function warnDeprecation(key: string, message: string) { if (!warnedDeprecations.has(key)) { @@ -72,60 +72,57 @@ type NeverOptional = 0 extends 1 & N ? Partial> : T; +/** + * Error message type for deprecated 'handler' property. + * Using a string literal type causes TypeScript to show this message in errors. + */ +type HANDLER_REMOVED_ERROR = + "⚠️ 'handler' was removed in @convex-dev/agent v0.6.0. Rename to 'execute'. See: node_modules/@convex-dev/agent/MIGRATION.md"; + export type ToolOutputPropertiesCtx< INPUT, OUTPUT, Ctx extends ToolCtx = ToolCtx, > = NeverOptional< OUTPUT, - | { - /** - * An async function that is called with the arguments from the tool call and produces a result. - * If `execute` (or `handler`) is not provided, the tool will not be executed automatically. - * - * @param input - The input of the tool call. - * @param options.abortSignal - A signal that can be used to abort the tool call. - */ - execute: ToolExecuteFunctionCtx; - outputSchema?: FlexibleSchema; - handler?: never; - } - | { - /** @deprecated Use execute instead. */ - handler: ToolExecuteFunctionCtx; - outputSchema?: FlexibleSchema; - execute?: never; - } - | { - outputSchema: FlexibleSchema; - execute?: never; - handler?: never; - } + { + /** + * An async function that is called with the arguments from the tool call and produces a result. + * If `execute` is not provided, the tool will not be executed automatically. + * + * @param input - The input of the tool call. + * @param options.abortSignal - A signal that can be used to abort the tool call. + */ + execute?: ToolExecuteFunctionCtx; + outputSchema?: FlexibleSchema; + /** + * @deprecated Removed in v0.6.0. Use `execute` instead. + */ + handler?: HANDLER_REMOVED_ERROR; + } >; -export type ToolInputProperties = - | { - /** - * The schema of the input that the tool expects. - * The language model will use this to generate the input. - * It is also used to validate the output of the language model. - * - * You can use descriptions on the schema properties to make the input understandable for the language model. - */ - inputSchema: FlexibleSchema; - args?: never; - } - | { - /** - * The schema of the input that the tool expects. The language model will use this to generate the input. - * It is also used to validate the output of the language model. - * Use descriptions to make the input understandable for the language model. - * - * @deprecated Use inputSchema instead. - */ - args: FlexibleSchema; - inputSchema?: never; - }; +/** + * Error message type for deprecated 'args' property. + * Using a string literal type causes TypeScript to show this message in errors. + */ +type ARGS_REMOVED_ERROR = + "⚠️ 'args' was removed in @convex-dev/agent v0.6.0. Rename to 'inputSchema'. See: node_modules/@convex-dev/agent/MIGRATION.md"; + +export type ToolInputProperties = { + /** + * The schema of the input that the tool expects. + * The language model will use this to generate the input. + * It is also used to validate the output of the language model. + * + * You can use descriptions on the schema properties to make the input understandable for the language model. + */ + inputSchema: FlexibleSchema; + /** + * @deprecated Removed in v0.6.0. Use `inputSchema` instead. + */ + args?: ARGS_REMOVED_ERROR; +}; /** * This is a wrapper around the ai.tool function that adds extra context to the @@ -238,24 +235,25 @@ export function createTool( ) => ToolResultOutput | PromiseLike; }, ): Tool { - const inputSchema = def.inputSchema ?? def.args; + // Runtime backwards compat - types will show errors but runtime still works + const inputSchema = def.inputSchema ?? (def as any).args; if (!inputSchema) - throw new Error("To use a Convex tool, you must provide an `inputSchema` (or `args`)"); + throw new Error("To use a Convex tool, you must provide an `inputSchema`"); - if (def.args && !def.inputSchema) { + if ((def as any).args && !def.inputSchema) { warnDeprecation( "createTool.args", "createTool: 'args' is deprecated. Use 'inputSchema' instead.", ); } - if (def.handler && !def.execute) { + if ((def as any).handler && !def.execute) { warnDeprecation( "createTool.handler", "createTool: 'handler' is deprecated. Use 'execute' instead.", ); } - const executeHandler = def.execute ?? def.handler; + const executeHandler = def.execute ?? (def as any).handler; if (!executeHandler && !def.outputSchema) throw new Error( "To use a Convex tool, you must either provide an execute" + diff --git a/src/client/index.ts b/src/client/index.ts index dd0041f9..75222764 100644 --- a/src/client/index.ts +++ b/src/client/index.ts @@ -19,7 +19,7 @@ import type { } from "ai"; import { generateObject, generateText, stepCountIs, streamObject } from "ai"; -const MIGRATION_URL = "https://github.com/get-convex/agent/blob/main/MIGRATION.md"; +const MIGRATION_URL = "node_modules/@convex-dev/agent/MIGRATION.md"; const warnedDeprecations = new Set(); function warnDeprecation(key: string, message: string) { if (!warnedDeprecations.has(key)) { diff --git a/src/client/types.ts b/src/client/types.ts index 2533d029..ea07439a 100644 --- a/src/client/types.ts +++ b/src/client/types.ts @@ -46,6 +46,14 @@ import type { import type { StreamingOptions } from "./streaming.js"; import type { ComponentApi } from "../component/_generated/component.js"; +/** + * Type-level check that ensures models are from AI SDK v6. + * If a v5 model (LanguageModelV2) is passed, TypeScript will show the error message string. + */ +type AssertAISDKv6 = T extends { specificationVersion: "v3" } + ? T + : "⚠️ @convex-dev/agent v0.6.0 requires AI SDK v6. Update your dependencies: npm install ai@^6.0.35 @ai-sdk/openai@^3.0.10 (or other provider). See: node_modules/@convex-dev/agent/MIGRATION.md"; + export type AgentPrompt = { /** * System message to include in the prompt. Overwrites Agent instructions. @@ -91,23 +99,17 @@ export type AgentPrompt = { export type Config = { /** * The LLM model to use for generating / streaming text and objects. - * e.g. + * Requires AI SDK v6 (@ai-sdk/* packages v3.x). + * + * @example * import { openai } from "@ai-sdk/openai" * const myAgent = new Agent(components.agent, { * languageModel: openai.chat("gpt-4o-mini"), + * }) */ - languageModel?: LanguageModel; + languageModel?: AssertAISDKv6; /** - * The model to use for text embeddings. Optional. - * If specified, it will use this for generating vector embeddings - * of chats, and can opt-in to doing vector search for automatic context - * on generateText, etc. - * e.g. - * import { openai } from "@ai-sdk/openai" - * const myAgent = new Agent(components.agent, { - * ... - * textEmbeddingModel: openai.embedding("text-embedding-3-small") - * @deprecated — Use embeddingModel instead. + * @deprecated Use `embeddingModel` instead. */ textEmbeddingModel?: EmbeddingModel; /** diff --git a/src/deltas.ts b/src/deltas.ts index 87a8f9f7..0e816d04 100644 --- a/src/deltas.ts +++ b/src/deltas.ts @@ -124,7 +124,6 @@ export async function deriveUIMessagesFromDeltas( blankUIMessage(streamMessage, threadId), parts, ); - // TODO: this fails on partial tool calls messages.push(uiMessage); } else { const [uiMessages] = deriveUIMessagesFromTextStreamParts(