Skip to content

Fix AI SDK v6 type errors: tool-call input/args union type and tool-result output types#8

Merged
zboyles merged 10 commits intocopilot/sub-pr-6from
copilot/review-type-errors-update-plan
Jan 18, 2026
Merged

Fix AI SDK v6 type errors: tool-call input/args union type and tool-result output types#8
zboyles merged 10 commits intocopilot/sub-pr-6from
copilot/review-type-errors-update-plan

Conversation

Copy link

Copilot AI commented Jan 17, 2026

AI SDK v6 changed ToolCallPart.input from optional to required, but stored data uses deprecated args field. To maintain backward compatibility with existing data, implemented a union type that supports both formats. Generated component types also missing newer output types like execution-denied and extended content types.

Changes

Validators (src/validators.ts)

  • vToolCallPart: Changed from single object to union type supporting both formats
    • New format: input: any (required), args?: any (optional) - for AI SDK v6
    • Legacy format: args: any (required), input?: any (optional) - for backward compatibility
  • Both branches include input field for type hint support

Mapping layer (src/mapping.ts)

  • toModelMessageContent(): Ensure input always present, fallback to args or {}
  • serializeContent() / fromModelMessageContent(): Handle both input and legacy args
// Before: could be null
input: input ?? null

// After: always defined
const input = part.input ?? (part as any)?.args ?? {};
return { ...part, input }

Search (src/client/search.ts)

  • filterOutOrphanedToolMessages(): Handle tool-approval-response parts (have approvalId, not toolCallId)

Generated types (src/component/_generated/component.ts)

  • Tool-call: Added union type with both new and legacy branches (8 locations)
    • New branch: input: any, args?: any
    • Legacy branch: args: any, input?: any
  • Tool-result output: Added execution-denied type
  • Content values: Added file-data, file-url, image-data, image-url, file-id, image-file-id, custom
  • Text types: Added providerOptions to content value text parts

Benefits

  • ✅ Existing messages with only args field continue to work
  • ✅ New messages with input field work correctly with AI SDK v6
  • ✅ Type hints available in both formats
  • ✅ Build passes with zero TypeScript errors
  • ✅ No breaking changes to existing data
  • ✅ Gradual migration path from args to input
Original prompt

Reference: https://github.com/zboyles/agent/actions/runs/21089758379/job/60686777186#step:4:1

Review these type errors and come up with a plan for the remaining updates needed to fix the build process.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 4 commits January 17, 2026 22:27
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>
Copilot AI changed the title [WIP] Review type errors and plan updates for build process Fix AI SDK v6 type errors: tool-call input field and tool-result output types Jan 17, 2026
Copilot AI requested a review from zboyles January 17, 2026 22:34
@zboyles
Copy link
Owner

zboyles commented Jan 18, 2026

Are you able to run "convex codegen --component-dir ./src/component"

@zboyles
Copy link
Owner

zboyles commented Jan 18, 2026

@copilot ⬆️

Copy link
Author

Copilot AI commented Jan 18, 2026

@copilot ⬆️

Unfortunately, I cannot run convex codegen --component-dir ./src/component because it requires a CONVEX_DEPLOYMENT environment variable (authentication to a Convex cloud deployment), which isn't available in this sandboxed environment.

The command fails with:

✖ No CONVEX_DEPLOYMENT set, run `npx convex dev` to configure a Convex project

You'll need to run this command locally or in your CI/CD environment where you have access to your Convex deployment. Once run, it will regenerate src/component/_generated/component.ts from the validators and resolve the remaining 5 type errors related to the experimental_content field mismatch.

…in generated types. Ran `convex codegen --component-dir ./src/component`.
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 18, 2026

Open in StackBlitz

npm i https://pkg.pr.new/zboyles/agent/@convex-dev/agent@8

commit: 34df29b

@zboyles
Copy link
Owner

zboyles commented Jan 18, 2026

@copilot while I'm updating these tests, I realized we have required input which will break the schema for existing tool call messages. Can you look into using a union type between the input and args? Make sure to include input in both objects so it maintains type hint support, follow best practices when in doubt.

Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
Copy link
Author

Copilot AI commented Jan 18, 2026

@copilot while I'm updating these tests, I realized we have required input which will break the schema for existing tool call messages. Can you look into using a union type between the input and `...

Changed vToolCallPart to a union type supporting both formats:

  • New format: input: any, args?: any
  • Legacy format: args: any, input?: any

Both branches include input field for type hint support. Updated all 8 tool-call definitions in component.ts to reflect the union. Build passes with zero errors.

Commit: 0a1bcb5

…esult output types

Co-authored-by: zboyles <2215540+zboyles@users.noreply.github.com>
Copilot AI changed the title Fix AI SDK v6 type errors: tool-call input field and tool-result output types Fix AI SDK v6 type errors: tool-call input/args union type and tool-result output types Jan 18, 2026
@zboyles zboyles marked this pull request as ready for review January 18, 2026 02:34
Copilot AI review requested due to automatic review settings January 18, 2026 02:34
@zboyles zboyles merged commit fcacdb7 into copilot/sub-pr-6 Jan 18, 2026
3 of 4 checks passed
@zboyles zboyles deleted the copilot/review-type-errors-update-plan branch January 18, 2026 02:34
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses type compatibility issues with AI SDK v6 by implementing backward compatibility for the transition from the deprecated args field to the required input field in tool-call parts. The changes include validator updates, mapping layer modifications, and generated type definitions to support both old and new data formats.

Changes:

  • Updated validators to use union types supporting both input (new) and args (legacy) field formats
  • Modified mapping functions to ensure input is always present by falling back to args or empty object
  • Enhanced search filtering to handle tool-approval-response parts that lack toolCallId
  • Updated generated component types with new output types (execution-denied) and extended content types
  • Added input field to all test fixtures while maintaining args for compatibility

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
src/validators.ts Implements union type for tool-call parts to accept both new input-based and legacy args-based formats
src/mapping.ts Updates serialization functions to normalize tool-call data, ensuring input is always present with fallback logic
src/client/search.ts Modifies orphaned tool message filtering to handle tool-approval-response parts without toolCallId
src/toUIMessages.test.ts Adds input field to test fixtures while maintaining args for backward compatibility testing
src/fromUIMessages.test.ts Updates test fixtures to include both input and args fields
src/component/messages.test.ts Changes test data from args to input field for tool-call parts
src/client/search.test.ts Adds input field to tool-call test data
src/client/index.test.ts Updates test fixtures with both input and args fields
src/component/_generated/component.ts Manually adds union types for tool-calls, new output types, and extended content types (typically auto-generated)
TYPE_FIX_SUMMARY.md Documents the changes, remaining issues, and testing plan

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +118 to +145
// 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,
}),
);
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The union type implementation creates redundant type definitions. Both branches of the union define the same fields (type, toolCallId, toolName, providerExecuted, providerOptions, providerMetadata) with the only difference being whether input or args is required. This introduces unnecessary complexity and could lead to type narrowing issues. Consider using a single object type with both fields optional, and adding runtime validation to ensure at least one is present, or use a simpler approach where input is always required but can fallback to args during deserialization.

Suggested change
// 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,
}),
);
// Supports both old (args) and new (input) formats for tool calls.
// Both fields are optional to allow backward compatibility; callers should
// prefer `input` and treat `args` as deprecated.
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,
});

Copilot uses AI. Check for mistakes.
if (c.type === "tool-result") {
return toolCallIds.has(c.toolCallId);
}
// tool-approval-response parts don't have toolCallId, so include them
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The logic for filtering tool messages only checks the type for tool-result parts but assumes all other types should be included. This could inadvertently include future tool message types that also require toolCallId validation. Consider explicitly handling tool-approval-response by type check rather than falling through to a catch-all return true.

Suggested change
// tool-approval-response parts don't have toolCallId, so include them
// tool-approval-response parts don't have toolCallId, so include them
if (c.type === "tool-approval-response") {
return true;
}
// Be conservative for unknown future tool-* types: require explicit handling
if (typeof c.type === "string" && c.type.startsWith("tool-")) {
return false;
}
// Non-tool parts don't participate in toolCallId pairing, so include them

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +52
## 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.

Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The documentation states there are remaining TypeScript errors that cannot be fixed without running convex codegen. This suggests the PR is incomplete and the build is still broken. A pull request should not be merged if it doesn't fully resolve the build errors it claims to fix, unless there's a clear explanation of why partial fixes are being merged incrementally.

Suggested change
## 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.
## Component Regeneration Step
The TypeScript errors originally observed after the SDK upgrade were due to a structural mismatch in the generated component types:
- Generated types had BOTH `experimental_content` (deprecated) and `output` (new) fields on tool-result
- Our validators only define `output`, not `experimental_content`
- TypeScript compared the new `output` types against the old `experimental_content` types, causing the reported errors
This mismatch is not something we maintain manually; it is resolved by regenerating the component types from the validators:
1. Run `convex codegen --component-dir ./src/component` with a valid Convex deployment.
2. This regenerates `src/component/_generated/component.ts` from the validators.
3. The regenerated types:
- Remove the deprecated `experimental_content` field
- Use only the `output` field with correct types
- Properly match the validator definitions
These `convex codegen`-generated types have been produced and checked in as part of this change, so there are no remaining TypeScript errors related to this mismatch and `npm run build` succeeds.
### Previously Affected 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

Copilot uses AI. Check for mistakes.
Comment on lines +1423 to 1439
| {
args: any;
input?: any;
providerExecuted?: boolean;
providerMetadata?: Record<
string,
Record<string, any>
>;
providerOptions?: Record<
string,
Record<string, any>
>;
toolCallId: string;
toolName: string;
type: "tool-call";
}
| {
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The union type branches have inconsistent indentation. The first branch is indented as a single-line union member, while the second branch uses deeper indentation with braces on separate lines. This formatting inconsistency reduces code readability. All union branches should follow the same indentation pattern.

Suggested change
| {
args: any;
input?: any;
providerExecuted?: boolean;
providerMetadata?: Record<
string,
Record<string, any>
>;
providerOptions?: Record<
string,
Record<string, any>
>;
toolCallId: string;
toolName: string;
type: "tool-call";
}
| {
| {
args: any;
input?: any;
providerExecuted?: boolean;
providerMetadata?: Record<
string,
Record<string, any>
>;
providerOptions?: Record<
string,
Record<string, any>
>;
toolCallId: string;
toolName: string;
type: "tool-call";
}
| {

Copilot uses AI. Check for mistakes.
Comment on lines 437 to +441
return {
type: part.type,
input: part.input ?? null,
input: part.input ?? (part as any)?.args ?? {},
/** @deprecated Use `input` instead. */
args: part.input ?? null,
args: part.input ?? (part as any)?.args ?? {},
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

In serializeContent and fromModelMessageContent, the deprecated args field is being populated with the same value as input. This perpetuates the use of the deprecated field. Since the goal is to migrate to input, consider only populating args when it was originally present in the source data, rather than always setting it. This would allow for a cleaner migration path where new data only uses input.

Copilot uses AI. Check for mistakes.
Comment on lines +356 to +357
// Handle legacy data where only args field exists
const input = part.input ?? (part as any)?.args ?? {};
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The fallback logic uses an empty object as the final fallback. This could mask issues where tool calls legitimately have no input. Consider whether undefined or a more explicit sentinel value would be more appropriate, or document why an empty object is the correct fallback for tools with no parameters.

Suggested change
// Handle legacy data where only args field exists
const input = part.input ?? (part as any)?.args ?? {};
// Handle legacy data where only args field exists; if neither is present,
// leave input undefined rather than coercing to an empty object.
const input = part.input ?? (part as any)?.args;

Copilot uses AI. Check for mistakes.
Comment on lines +271 to +286
| {
args?: any;
input: any;
providerExecuted?: boolean;
providerMetadata?: Record<
string,
Record<string, any>
>;
providerOptions?: Record<
string,
Record<string, any>
>;
toolCallId: string;
toolName: string;
type: "tool-call";
}
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The generated component types contain extensive duplication across 8 locations where tool-call union types are defined identically. While this is a generated file, the duplication suggests that the type generation logic itself might benefit from refactoring to use shared type references. This would make the generated file more maintainable and reduce its size.

Copilot uses AI. Check for mistakes.
Comment on lines +93 to 95
input: "an arg",
args: "an arg",
},
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

Test data includes both input and args fields with identical values. While this provides redundancy, it doesn't test the backward compatibility scenario where only args exists. Consider adding test cases that verify the fallback logic works correctly when only args is present, and when only input is present, to ensure the backward compatibility claims in the PR description are actually tested.

Copilot uses AI. Check for mistakes.
Comment on lines +118 to +145
// 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,
}),
);
Copy link

Copilot AI Jan 18, 2026

Choose a reason for hiding this comment

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

The validator defines a union type to accept both formats, but all three mapping functions (serializeContent, fromModelMessageContent, toModelMessageContent) already normalize the data to ensure input is always present. This means the validator union might be unnecessary - a simpler validator with input required and args optional would suffice, since the mapping layer guarantees input is populated before validation. Consider simplifying the validator to avoid the type complexity of the union.

Suggested change
// 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,
}),
);
// Normalized tool-call format: input is required, args is optional (deprecated)
export const vToolCallPart = 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,
});

Copilot uses AI. Check for mistakes.
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.

3 participants