[WIP] Update createTool to support AI SDK v5 and v6 features#7
[WIP] Update createTool to support AI SDK v5 and v6 features#7zboyles merged 15 commits intozboyles/ai-sdk-v6/add-tool-approvalfrom
Conversation
- Updated component schema to use optional mediaType and mimeType - Updated files.ts addFile mutation to accept both mediaType and mimeType - Updated client files.ts to use mediaType when calling component - Updated getFile to support both mediaType and mimeType from database - Updated mapping.ts to return mediaType instead of mimeType - Maintained backwards compatibility throughout Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
- Updated all file part definitions in generated component.ts - Changed mimeType from required to optional - Added mediaType as optional field - Resolved all TypeScript type errors related to mimeType/mediaType Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
…t types Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
…in generated types. Ran `convex codegen --component-dir ./src/component`.
…itions for consistency
Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
…esult output types Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
…plan Fix AI SDK v6 type errors: tool-call input/args union type and tool-result output types
commit: |
37e0e75
into
zboyles/ai-sdk-v6/add-tool-approval
There was a problem hiding this comment.
Pull request overview
This pull request implements backwards-compatible migration from mimeType to mediaType for file handling and adds support for both input and deprecated args fields in tool-call parts to support AI SDK v6. The changes are part of a larger effort to update the agent system to work with AI SDK v5 and v6 features.
Changes:
- Migrates from
mimeTypetomediaTypeacross validators, schema, mapping functions, and file handling while maintaining backwards compatibility - Updates tool-call part handling to support both new
inputand legacyargsfields with fallback logic - Fixes tool-approval-response filtering in search functionality to handle parts without
toolCallId - Updates all tests to include
inputfield in tool-call parts - Manually updates generated component types (temporary until proper regeneration)
Reviewed changes
Copilot reviewed 12 out of 13 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/validators.ts | Adds union type for vToolCallPart to support both input/args formats |
| src/mapping.ts | Updates all mapping functions to use mediaType and handle tool-call input/args fallback |
| src/component/schema.ts | Makes both mediaType and mimeType optional in files table |
| src/component/files.ts | Updates addFile handler to support both mediaType and mimeType with fallback logic |
| src/client/files.ts | Updates storeFile to use mediaType, getFile to support fallback to mimeType |
| src/client/search.ts | Fixes filterOutOrphanedToolMessages to handle tool-approval-response parts |
| src/toUIMessages.test.ts | Adds input field to all tool-call test data |
| src/fromUIMessages.test.ts | Adds input field and verifies mediaType conversion |
| src/component/messages.test.ts | Updates tool-call test data to use input field |
| src/client/search.test.ts | Adds input field to tool-call test data |
| src/client/index.test.ts | Adds input field to tool-call test data |
| src/component/_generated/component.ts | Manual updates to generated types (requires proper regeneration) |
| TYPE_FIX_SUMMARY.md | Documentation of migration work and remaining issues |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| @@ -56,7 +56,8 @@ export type ComponentApi<Name extends string | undefined = string | undefined> = | |||
| { | |||
| filename?: string; | |||
| hash: string; | |||
| mimeType: string; | |||
| mediaType?: string; | |||
| mimeType?: string; | |||
There was a problem hiding this comment.
This file is auto-generated and should not be manually edited. The changes made here will be overwritten the next time convex codegen is run. As noted in TYPE_FIX_SUMMARY.md, these manual edits are temporary and the file needs to be properly regenerated from validators using convex codegen --component-dir ./src/component with a valid Convex deployment.
| mimeType?: string; | |
| mimeType: string; |
| # AI SDK v6 Type Error Fix Summary | ||
|
|
||
| ## Problem | ||
| The build fails with TypeScript errors after upgrading to AI SDK v6. The main issues are: | ||
| 1. `ToolCallPart` type now requires `input` field (not optional), but stored data may only have deprecated `args` field | ||
| 2. Tool-result output types missing newer types like `execution-denied` and extended content types | ||
| 3. Generated component types out of sync with updated validators | ||
|
|
||
| ## Changes Made | ||
|
|
||
| ### 1. Fixed `tool-call` Part Handling (src/mapping.ts) | ||
| - Updated `toModelMessageContent()` to ensure `input` is always present by falling back to `args` or `{}` | ||
| - Updated `serializeContent()` and `fromModelMessageContent()` to handle both `input` and legacy `args` fields | ||
| - This fixes the core issue where AI SDK v6's `ToolCallPart` expects non-nullable `input` | ||
|
|
||
| ### 2. Fixed Tool Approval Response Handling (src/client/search.ts) | ||
| - Updated `filterOutOrphanedToolMessages()` to handle tool-approval-response parts that don't have `toolCallId` | ||
| - Tool-approval-response only has `approvalId`, not `toolCallId` | ||
|
|
||
| ### 3. Updated Generated Component Types (src/component/_generated/component.ts) | ||
| Made manual updates to sync with validators (normally done via `convex codegen`): | ||
| - Added `input: any` field to all tool-call type definitions | ||
| - Made `args` optional (`args?: any`) in tool-call types | ||
| - Added `execution-denied` output type to tool-result | ||
| - Added extended content types: `file-data`, `file-url`, `file-id`, `image-data`, `image-url`, `image-file-id`, `custom` | ||
| - Added `providerOptions` to text types in content values | ||
|
|
||
| ## Remaining Issues (5 TypeScript errors) | ||
|
|
||
| The remaining errors are due to a structural mismatch in the generated component types: | ||
| - Generated types have BOTH `experimental_content` (deprecated) and `output` (new) fields on tool-result | ||
| - Our validators only define `output`, not `experimental_content` | ||
| - TypeScript is comparing our new output types against the old experimental_content types | ||
| - This cannot be fixed manually - requires proper component regeneration | ||
|
|
||
| ### To Complete the Fix: | ||
| 1. Run `convex codegen --component-dir ./src/component` with a valid Convex deployment | ||
| 2. This will regenerate `src/component/_generated/component.ts` from the validators | ||
| 3. The regenerated types will: | ||
| - Remove the deprecated `experimental_content` field | ||
| - Use only the `output` field with correct types | ||
| - Properly match the validator definitions | ||
|
|
||
| ### Error Locations: | ||
| - `src/client/index.ts:1052` - addMessages call | ||
| - `src/client/index.ts:1103` - addMessages call | ||
| - `src/client/index.ts:1169` - updateMessage call | ||
| - `src/client/messages.ts:141` - addMessages call | ||
| - `src/client/start.ts:265` - addMessages call | ||
|
|
||
| All errors have the same root cause: content value types in tool-result output don't match experimental_content expectations. | ||
|
|
||
| ## Testing Plan | ||
| Once component types are regenerated: | ||
| 1. Run `npm run build` - should complete without errors | ||
| 2. Run `npm test` - ensure no regressions | ||
| 3. Test with actual AI SDK v6 workflow - verify tool-call handling works with both new `input` and legacy `args` fields | ||
|
|
||
| ## Notes | ||
| - The mapping functions in `src/mapping.ts` correctly handle both old and new formats | ||
| - Data with only `args` will be converted to have `input` (with `args` as fallback) | ||
| - Data with `input` will work directly | ||
| - This provides backward compatibility while supporting AI SDK v6's requirements |
There was a problem hiding this comment.
This documentation file provides excellent context about the migration work and remaining issues. However, it should not be committed to the main codebase as it's implementation-specific documentation. Consider moving this content to the PR description, a GitHub issue, or a separate documentation folder for migration notes.
| # AI SDK v6 Type Error Fix Summary | |
| ## Problem | |
| The build fails with TypeScript errors after upgrading to AI SDK v6. The main issues are: | |
| 1. `ToolCallPart` type now requires `input` field (not optional), but stored data may only have deprecated `args` field | |
| 2. Tool-result output types missing newer types like `execution-denied` and extended content types | |
| 3. Generated component types out of sync with updated validators | |
| ## Changes Made | |
| ### 1. Fixed `tool-call` Part Handling (src/mapping.ts) | |
| - Updated `toModelMessageContent()` to ensure `input` is always present by falling back to `args` or `{}` | |
| - Updated `serializeContent()` and `fromModelMessageContent()` to handle both `input` and legacy `args` fields | |
| - This fixes the core issue where AI SDK v6's `ToolCallPart` expects non-nullable `input` | |
| ### 2. Fixed Tool Approval Response Handling (src/client/search.ts) | |
| - Updated `filterOutOrphanedToolMessages()` to handle tool-approval-response parts that don't have `toolCallId` | |
| - Tool-approval-response only has `approvalId`, not `toolCallId` | |
| ### 3. Updated Generated Component Types (src/component/_generated/component.ts) | |
| Made manual updates to sync with validators (normally done via `convex codegen`): | |
| - Added `input: any` field to all tool-call type definitions | |
| - Made `args` optional (`args?: any`) in tool-call types | |
| - Added `execution-denied` output type to tool-result | |
| - Added extended content types: `file-data`, `file-url`, `file-id`, `image-data`, `image-url`, `image-file-id`, `custom` | |
| - Added `providerOptions` to text types in content values | |
| ## Remaining Issues (5 TypeScript errors) | |
| The remaining errors are due to a structural mismatch in the generated component types: | |
| - Generated types have BOTH `experimental_content` (deprecated) and `output` (new) fields on tool-result | |
| - Our validators only define `output`, not `experimental_content` | |
| - TypeScript is comparing our new output types against the old experimental_content types | |
| - This cannot be fixed manually - requires proper component regeneration | |
| ### To Complete the Fix: | |
| 1. Run `convex codegen --component-dir ./src/component` with a valid Convex deployment | |
| 2. This will regenerate `src/component/_generated/component.ts` from the validators | |
| 3. The regenerated types will: | |
| - Remove the deprecated `experimental_content` field | |
| - Use only the `output` field with correct types | |
| - Properly match the validator definitions | |
| ### Error Locations: | |
| - `src/client/index.ts:1052` - addMessages call | |
| - `src/client/index.ts:1103` - addMessages call | |
| - `src/client/index.ts:1169` - updateMessage call | |
| - `src/client/messages.ts:141` - addMessages call | |
| - `src/client/start.ts:265` - addMessages call | |
| All errors have the same root cause: content value types in tool-result output don't match experimental_content expectations. | |
| ## Testing Plan | |
| Once component types are regenerated: | |
| 1. Run `npm run build` - should complete without errors | |
| 2. Run `npm test` - ensure no regressions | |
| 3. Test with actual AI SDK v6 workflow - verify tool-call handling works with both new `input` and legacy `args` fields | |
| ## Notes | |
| - The mapping functions in `src/mapping.ts` correctly handle both old and new formats | |
| - Data with only `args` will be converted to have `input` (with `args` as fallback) | |
| - Data with `input` will work directly | |
| - This provides backward compatibility while supporting AI SDK v6's requirements | |
| # Type Fix Summary | |
| This file is intentionally left blank. Migration-specific implementation details have been moved to external developer documentation. |
| // Union type to support both old (args) and new (input) formats | ||
| // Both include input for type hint support | ||
| export const vToolCallPart = v.union( | ||
| // New format: input is primary, args is optional for backwards compat | ||
| v.object({ | ||
| type: v.literal("tool-call"), | ||
| toolCallId: v.string(), | ||
| toolName: v.string(), | ||
| input: v.any(), | ||
| /** @deprecated Use `input` instead. */ | ||
| args: v.optional(v.any()), | ||
| providerExecuted: v.optional(v.boolean()), | ||
| providerOptions, | ||
| providerMetadata, | ||
| }), | ||
| // Legacy format: args is present, input is optional | ||
| v.object({ | ||
| type: v.literal("tool-call"), | ||
| toolCallId: v.string(), | ||
| toolName: v.string(), | ||
| /** @deprecated Use `input` instead. */ | ||
| args: v.any(), | ||
| input: v.optional(v.any()), | ||
| providerExecuted: v.optional(v.boolean()), | ||
| providerOptions, | ||
| providerMetadata, | ||
| }), | ||
| ); |
There was a problem hiding this comment.
The union approach for backward compatibility creates ambiguity. Both union branches allow the same object structure, which can lead to unpredictable type inference and validation issues. When an object has both input and args fields, it's unclear which branch of the union it matches.
Consider using a single object definition where either input or args is required, but both can be present. For example:
- Make input optional with a custom validator that ensures at least one of input or args exists
- Or use a discriminator field to distinguish between old and new formats
| // Union type to support both old (args) and new (input) formats | |
| // Both include input for type hint support | |
| export const vToolCallPart = v.union( | |
| // New format: input is primary, args is optional for backwards compat | |
| v.object({ | |
| type: v.literal("tool-call"), | |
| toolCallId: v.string(), | |
| toolName: v.string(), | |
| input: v.any(), | |
| /** @deprecated Use `input` instead. */ | |
| args: v.optional(v.any()), | |
| providerExecuted: v.optional(v.boolean()), | |
| providerOptions, | |
| providerMetadata, | |
| }), | |
| // Legacy format: args is present, input is optional | |
| v.object({ | |
| type: v.literal("tool-call"), | |
| toolCallId: v.string(), | |
| toolName: v.string(), | |
| /** @deprecated Use `input` instead. */ | |
| args: v.any(), | |
| input: v.optional(v.any()), | |
| providerExecuted: v.optional(v.boolean()), | |
| providerOptions, | |
| providerMetadata, | |
| }), | |
| ); | |
| // Single type to support both old (args) and new (input) formats. | |
| // Both fields are optional; at least one is expected by convention. | |
| export const vToolCallPart = v.object({ | |
| type: v.literal("tool-call"), | |
| toolCallId: v.string(), | |
| toolName: v.string(), | |
| input: v.optional(v.any()), | |
| /** @deprecated Use `input` instead. */ | |
| args: v.optional(v.any()), | |
| providerExecuted: v.optional(v.boolean()), | |
| providerOptions, | |
| providerMetadata, | |
| }); |
| // Handle legacy data where only args field exists | ||
| const input = part.input ?? (part as any)?.args ?? {}; |
There was a problem hiding this comment.
The fallback chain part.input ?? (part as any)?.args ?? {} will default to an empty object when both input and args are undefined. This could mask issues where tool calls legitimately should have no arguments versus cases where the data is malformed. Consider whether an empty object is the correct default, or if you should throw an error or log a warning when both fields are missing.
| return { | ||
| type: part.type, | ||
| input: input ?? null, | ||
| input, | ||
| /** @deprecated Use `input` instead. */ | ||
| args: input ?? null, | ||
| args: input, |
There was a problem hiding this comment.
Setting both input and args to the same value maintains backward compatibility, but could lead to data inconsistency. If code modifies one field but not the other, they'll become out of sync. Consider documenting that these fields should always be kept in sync, or add validation to ensure they remain consistent.
| ) { | ||
| // Support both mediaType (preferred) and mimeType (deprecated) | ||
| const mediaType = args.mediaType ?? args.mimeType; | ||
|
|
There was a problem hiding this comment.
There's a trailing empty line after the comment that should be removed for consistent code style.
| const fileId = await ctx.db.insert("files", { | ||
| ...args, | ||
| storageId: args.storageId, | ||
| hash: args.hash, | ||
| filename: args.filename, | ||
| mediaType, | ||
| mimeType: args.mimeType, // Keep for backwards compatibility |
There was a problem hiding this comment.
The mediaType field is set from the args parameter, but if neither mediaType nor mimeType is provided in args, this will be undefined. The database schema allows optional values, but you should verify this is the intended behavior. Consider whether files should require a media type, or if there should be a default value.
| // tool-result parts have toolCallId | ||
| if (c.type === "tool-result") { | ||
| return toolCallIds.has(c.toolCallId); | ||
| } | ||
| // tool-approval-response parts don't have toolCallId, so include them |
There was a problem hiding this comment.
The filtering logic assumes that any part that isn't a tool-result should be included. However, tool-approval-request parts (which also exist in tool messages according to the validators) also have a toolCallId field. Consider explicitly checking for tool-approval-response type rather than relying on the absence of a toolCallId check.
| // tool-result parts have toolCallId | |
| if (c.type === "tool-result") { | |
| return toolCallIds.has(c.toolCallId); | |
| } | |
| // tool-approval-response parts don't have toolCallId, so include them | |
| // Filter parts that are tied to a specific tool call by toolCallId. | |
| if (c.type === "tool-result" || c.type === "tool-approval-request") { | |
| return toolCallIds.has(c.toolCallId); | |
| } | |
| // tool-approval-response and other non-call-bound parts are always included. |
Thanks for the feedback on #6. I've created this new PR, which merges into #6, to address your comment. I will work on the changes and keep this PR's description up to date as I make progress.
Original PR: #6
Triggering comment (#6 (comment)):
💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.