Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
b45fd52
Enhance createTool function to match AI SDK v6 additional properties …
zboyles Jan 17, 2026
676d067
Relaxed ToolInputProperties type definition
zboyles Jan 17, 2026
b6acbf3
Refine createTool function documentation and type definitions
zboyles Jan 17, 2026
b1624a9
Refactor validators to introduce new tool approval request and respon…
zboyles Jan 17, 2026
b1e0df9
Implement tool approval request and response handling in mapping and …
zboyles Jan 17, 2026
a9c4598
Add providerExecuted flag to tool approval response validator and map…
zboyles Jan 17, 2026
9376e9b
Initial plan
Copilot Jan 17, 2026
19692bf
Update mimeType to mediaType with backwards compatibility
Copilot Jan 17, 2026
dcdd9ea
Fix test to expect mediaType instead of mimeType
Copilot Jan 17, 2026
d051529
Update generated component types to use mediaType instead of mimeType
Copilot Jan 17, 2026
392152f
Initial plan
Copilot Jan 17, 2026
1e3f63c
Fix tool-call input field and add execution-denied output type
Copilot Jan 17, 2026
7d85495
Remove backup file
Copilot Jan 17, 2026
3ee5263
Add tool-call input field, execution-denied type, and extended conten…
Copilot Jan 17, 2026
2987530
Add summary document for type error fixes
Copilot Jan 17, 2026
1e7c351
Fix type errors due to experimental_content vs output field mismatch …
zboyles Jan 18, 2026
219f520
Refactor test cases to replace 'args' with 'input' in tool-call defin…
zboyles Jan 18, 2026
0a1bcb5
Use union type for tool-call to support both input and args formats
Copilot Jan 18, 2026
ca35f10
Fix AI SDK v6 type errors: tool-call input/args union type and tool-r…
Copilot Jan 18, 2026
34df29b
Added `input` values to the test types, kept `args`
zboyles Jan 18, 2026
fcacdb7
Merge pull request #8 from zboyles/copilot/review-type-errors-update-…
zboyles Jan 18, 2026
37e0e75
Merge pull request #7 from zboyles/copilot/sub-pr-6
zboyles Jan 18, 2026
06358df
Add support for tool approval workflow in deltas and UI messages
zboyles Jan 18, 2026
d0a161c
Updated `serializeContent` to include cases for `tool-approval-reques…
zboyles Jan 18, 2026
c9c54cd
Refactor to ensure the tool call IS present when the SDK runs `collec…
zboyles Jan 19, 2026
47c4780
Refactor GetEmbedding type to support optional embeddingModel and imp…
zboyles Jan 19, 2026
c1abfc3
Fix streaming error after tool approval by suppressing orphaned tool-…
zboyles Jan 19, 2026
f07fdd5
Silently suppress tool invocation errors in continuation stream after…
zboyles Jan 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions TYPE_FIX_SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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
102 changes: 98 additions & 4 deletions src/UIMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,10 @@ export async function fromUIMessages<METADATA = unknown>(
);
if (partWithProviderOptions?.providerOptions) {
// convertToModelMessages changes providerMetadata to providerOptions
const providerOptions = partWithProviderOptions.providerOptions as
| Record<string, Record<string, unknown>>
| undefined;
const providerOptions =
partWithProviderOptions.providerOptions as
| Record<string, Record<string, unknown>>
| undefined;
if (providerOptions) {
doc.providerMetadata = providerOptions;
doc.providerOptions ??= providerOptions;
Expand Down Expand Up @@ -477,8 +478,39 @@ function createAssistantUIMessage<
}
case "tool-result": {
const typedPart = contentPart as unknown as ToolResultPart & {
output: { type: string; value: unknown };
output: { type: string; value?: unknown; reason?: string };
};

// Check if this is an execution-denied result
if (typedPart.output?.type === "execution-denied") {
const call = allParts.find(
(part) =>
part.type === `tool-${contentPart.toolName}` &&
"toolCallId" in part &&
part.toolCallId === contentPart.toolCallId,
) as ToolUIPart | undefined;

if (call) {
call.state = "output-denied";
if (!("approval" in call) || !call.approval) {
(call as ToolUIPart & { approval?: object }).approval = {
id: "",
approved: false,
reason: typedPart.output.reason,
};
} else {
const approval = (
call as ToolUIPart & {
approval: { approved?: boolean; reason?: string };
}
).approval;
approval.approved = false;
approval.reason = typedPart.output.reason;
}
}
break;
}

const output =
typeof typedPart.output?.type === "string"
? typedPart.output.value
Expand Down Expand Up @@ -531,6 +563,68 @@ function createAssistantUIMessage<
}
break;
}
case "tool-approval-request": {
// Find the matching tool call
const typedPart = contentPart as {
toolCallId: string;
approvalId: string;
};
const toolCallPart = allParts.find(
(part) =>
"toolCallId" in part && part.toolCallId === typedPart.toolCallId,
) as ToolUIPart | undefined;

if (toolCallPart) {
toolCallPart.state = "approval-requested";
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
id: typedPart.approvalId,
};
} else {
console.warn(
"Tool approval request without preceding tool call",
contentPart,
);
}
break;
}
case "tool-approval-response": {
// Find the tool call that has this approval by matching approval.id
const typedPart = contentPart as {
approvalId: string;
approved: boolean;
reason?: string;
};
const toolCallPart = allParts.find(
(part) =>
"approval" in part &&
(part as ToolUIPart & { approval?: { id: string } }).approval
?.id === typedPart.approvalId,
) as ToolUIPart | undefined;

if (toolCallPart) {
if (typedPart.approved) {
toolCallPart.state = "approval-responded";
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
id: typedPart.approvalId,
approved: true,
reason: typedPart.reason,
};
} else {
toolCallPart.state = "output-denied";
(toolCallPart as ToolUIPart & { approval?: object }).approval = {
id: typedPart.approvalId,
approved: false,
reason: typedPart.reason,
};
}
} else {
console.warn(
"Tool approval response without matching approval request",
contentPart,
);
}
break;
}
default: {
const maybeSource = contentPart as unknown as SourcePart;
if (maybeSource.type === "source") {
Expand Down
Loading
Loading