Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 17 additions & 0 deletions .changeset/nine-starfishes-relax.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
"@ai-sdk/openai-compatible": patch
"@ai-sdk/amazon-bedrock": patch
"@ai-sdk/open-responses": patch
"@ai-sdk/anthropic": patch
"@ai-sdk/deepseek": patch
"@ai-sdk/alibaba": patch
"@ai-sdk/mistral": patch
"@ai-sdk/cohere": patch
"@ai-sdk/google": patch
"@ai-sdk/openai": patch
"@ai-sdk/groq": patch
"@ai-sdk/xai": patch
"ai": patch
---

fix: more precise default message for tool execution denial
2 changes: 1 addition & 1 deletion packages/ai/src/ui/convert-to-model-messages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1940,7 +1940,7 @@ describe('convertToModelMessages', () => {
{
"output": {
"type": "error-text",
"value": "Tool execution denied.",
"value": "Tool call execution denied.",
},
"toolCallId": "call-1",
"toolName": "weather",
Expand Down
2 changes: 1 addition & 1 deletion packages/ai/src/ui/convert-to-model-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ export async function convertToModelMessages<UI_MESSAGE extends UIMessage>(
type: 'error-text' as const,
value:
toolPart.approval?.reason ??
'Tool execution denied.',
'Tool call execution denied.',
},
...(toolPart.callProviderMetadata != null
? { providerOptions: toolPart.callProviderMetadata }
Expand Down
2 changes: 1 addition & 1 deletion packages/alibaba/src/convert-to-alibaba-chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ export function convertToAlibabaChatMessages({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ export async function convertToBedrockChatMessages(
break;
case 'execution-denied':
toolResultContent = [
{ text: output.reason ?? 'Tool execution denied.' },
{ text: output.reason ?? 'Tool call execution denied.' },
];
break;
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,8 @@ export async function convertToAnthropicMessagesPrompt({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue =
output.reason ?? 'Tool call execution denied.';
break;
case 'json':
case 'error-json':
Expand Down
2 changes: 1 addition & 1 deletion packages/cohere/src/convert-to-cohere-chat-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ export async function convertToCohereChatPrompt(
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export function convertToDeepSeekChatMessages({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ export function convertToGoogleGenerativeAIMessages(
name: part.toolName,
content:
output.type === 'execution-denied'
? (output.reason ?? 'Tool execution denied.')
? (output.reason ?? 'Tool call execution denied.')
: output.value,
},
},
Expand Down
2 changes: 1 addition & 1 deletion packages/groq/src/convert-to-groq-chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function convertToGroqChatMessages(
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
2 changes: 1 addition & 1 deletion packages/mistral/src/convert-to-mistral-chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export function convertToMistralChatMessages(
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export async function convertToOpenResponsesInput({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'json':
case 'error-json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ export function convertToOpenAICompatibleChatMessages(
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ export function convertToOpenAIChatMessages({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2332,6 +2332,39 @@ describe('convertToOpenAIResponsesInput', () => {
`);
});

it('should convert execution-denied tool result to function_call_output', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
prompt: [
{
role: 'tool',
content: [
{
type: 'tool-result',
toolCallId: 'call_denied_123',
toolName: 'search',
output: {
type: 'execution-denied',
reason: 'User denied the tool execution',
},
},
],
},
],
systemMessageMode: 'system',
providerOptionsName: 'openai',
store: true,
});

expect(result.input).toEqual([
{
type: 'function_call_output',
call_id: 'call_denied_123',
output: 'User denied the tool execution',
},
]);
});

it('should convert single tool result part with multipart that contains text', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
Expand Down Expand Up @@ -3241,6 +3274,127 @@ describe('convertToOpenAIResponsesInput', () => {
`);
});

it('should skip provider-executed execution-denied tool results in assistant messages', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
prompt: [
{
role: 'assistant',
content: [
{
type: 'text',
text: 'I need approval before running that tool.',
},
{
type: 'tool-result',
toolCallId: 'ws_denied_123',
toolName: 'web_search',
output: {
type: 'execution-denied',
reason: 'User denied the tool execution',
},
},
{
type: 'text',
text: 'The tool was not run.',
},
],
},
],
systemMessageMode: 'system',
providerOptionsName: 'openai',
store: false,
});

expect(result).toEqual({
input: [
{
role: 'assistant',
content: [
{
type: 'output_text',
text: 'I need approval before running that tool.',
},
],
id: undefined,
},
{
role: 'assistant',
content: [
{
type: 'output_text',
text: 'The tool was not run.',
},
],
id: undefined,
},
],
warnings: [],
});
});

it('should skip json-wrapped execution-denied tool results in assistant messages', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
prompt: [
{
role: 'assistant',
content: [
{
type: 'text',
text: 'I need approval before running that tool.',
},
{
type: 'tool-result',
toolCallId: 'ws_denied_json_123',
toolName: 'web_search',
output: {
type: 'json',
value: {
type: 'execution-denied',
reason: 'User denied the tool execution',
},
},
},
{
type: 'text',
text: 'The tool was not run.',
},
],
},
],
systemMessageMode: 'system',
providerOptionsName: 'openai',
store: false,
});

expect(result).toEqual({
input: [
{
role: 'assistant',
content: [
{
type: 'output_text',
text: 'I need approval before running that tool.',
},
],
id: undefined,
},
{
role: 'assistant',
content: [
{
type: 'output_text',
text: 'The tool was not run.',
},
],
id: undefined,
},
],
warnings: [],
});
});

describe('local shell', () => {
it('should convert local shell tool call and result into item reference with store: true', async () => {
const result = await convertToOpenAIResponsesInput({
Expand Down Expand Up @@ -4599,6 +4753,40 @@ describe('convertToOpenAIResponsesInput', () => {
`);
});

it('should convert execution-denied custom tool result to custom_tool_call_output', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
prompt: [
{
role: 'tool',
content: [
{
type: 'tool-result',
toolCallId: 'call_custom_denied_001',
toolName: 'write_sql',
output: {
type: 'execution-denied',
reason: 'User denied the tool execution',
},
},
],
},
],
systemMessageMode: 'system',
providerOptionsName: 'openai',
store: true,
customProviderToolNames,
});

expect(result.input).toEqual([
{
type: 'custom_tool_call_output',
call_id: 'call_custom_denied_001',
output: 'User denied the tool execution',
},
]);
});

it('should convert custom tool result content output', async () => {
const result = await convertToOpenAIResponsesInput({
toolNameMapping: testToolNameMapping,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ export async function convertToOpenAIResponsesInput({
outputValue = output.value;
break;
case 'execution-denied':
outputValue = output.reason ?? 'Tool execution denied.';
outputValue = output.reason ?? 'Tool call execution denied.';
break;
case 'json':
case 'error-json':
Expand Down Expand Up @@ -801,7 +801,7 @@ export async function convertToOpenAIResponsesInput({
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'json':
case 'error-json':
Expand Down
2 changes: 1 addition & 1 deletion packages/xai/src/convert-to-xai-chat-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export function convertToXaiChatMessages(prompt: LanguageModelV3Prompt): {
contentValue = output.value;
break;
case 'execution-denied':
contentValue = output.reason ?? 'Tool execution denied.';
contentValue = output.reason ?? 'Tool call execution denied.';
break;
case 'content':
case 'json':
Expand Down
Loading