Skip to content

Commit c86e779

Browse files
committed
fix(providers): correct tool calling message format across all providers
1 parent 0bc245b commit c86e779

File tree

9 files changed

+316
-57
lines changed

9 files changed

+316
-57
lines changed

apps/sim/lib/mcp/client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ export class McpClient {
155155
return result.tools.map((tool: Tool) => ({
156156
name: tool.name,
157157
description: tool.description,
158-
inputSchema: tool.inputSchema,
158+
inputSchema: tool.inputSchema as McpTool['inputSchema'],
159159
serverId: this.config.id,
160160
serverName: this.config.name,
161161
}))

apps/sim/lib/mcp/types.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,29 @@ export interface McpSecurityPolicy {
5757
auditLevel: 'none' | 'basic' | 'detailed'
5858
}
5959

60+
/**
61+
* JSON Schema property definition for tool parameters.
62+
* Follows JSON Schema specification with description support.
63+
*/
64+
export interface McpToolSchemaProperty {
65+
type: string
66+
description?: string
67+
items?: McpToolSchemaProperty
68+
properties?: Record<string, McpToolSchemaProperty>
69+
required?: string[]
70+
enum?: Array<string | number | boolean>
71+
default?: unknown
72+
}
73+
6074
/**
6175
* JSON Schema for tool input parameters.
6276
* Aligns with MCP SDK's Tool.inputSchema structure.
6377
*/
6478
export interface McpToolSchema {
6579
type: 'object'
66-
properties?: Record<string, unknown>
80+
properties?: Record<string, McpToolSchemaProperty>
6781
required?: string[]
82+
description?: string
6883
}
6984

7085
/**

apps/sim/providers/anthropic/index.ts

Lines changed: 95 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { MAX_TOOL_ITERATIONS } from '@/providers'
66
import {
77
checkForForcedToolUsage,
88
createReadableStreamFromAnthropicStream,
9-
generateToolUseId,
109
} from '@/providers/anthropic/utils'
1110
import {
1211
getMaxOutputTokensForModel,
@@ -433,11 +432,32 @@ export const anthropicProvider: ProviderConfig = {
433432

434433
const executionResults = await Promise.allSettled(toolExecutionPromises)
435434

435+
// Collect all tool_use and tool_result blocks for batching
436+
const toolUseBlocks: Array<{
437+
type: 'tool_use'
438+
id: string
439+
name: string
440+
input: Record<string, unknown>
441+
}> = []
442+
const toolResultBlocks: Array<{
443+
type: 'tool_result'
444+
tool_use_id: string
445+
content: string
446+
}> = []
447+
436448
for (const settledResult of executionResults) {
437449
if (settledResult.status === 'rejected' || !settledResult.value) continue
438450

439-
const { toolName, toolArgs, toolParams, result, startTime, endTime, duration } =
440-
settledResult.value
451+
const {
452+
toolUse,
453+
toolName,
454+
toolArgs,
455+
toolParams,
456+
result,
457+
startTime,
458+
endTime,
459+
duration,
460+
} = settledResult.value
441461

442462
timeSegments.push({
443463
type: 'tool',
@@ -447,7 +467,7 @@ export const anthropicProvider: ProviderConfig = {
447467
duration: duration,
448468
})
449469

450-
let resultContent: any
470+
let resultContent: unknown
451471
if (result.success) {
452472
toolResults.push(result.output)
453473
resultContent = result.output
@@ -469,29 +489,34 @@ export const anthropicProvider: ProviderConfig = {
469489
success: result.success,
470490
})
471491

472-
const toolUseId = generateToolUseId(toolName)
492+
// Add to batched arrays using the ORIGINAL ID from Claude's response
493+
toolUseBlocks.push({
494+
type: 'tool_use',
495+
id: toolUse.id,
496+
name: toolName,
497+
input: toolArgs,
498+
})
499+
500+
toolResultBlocks.push({
501+
type: 'tool_result',
502+
tool_use_id: toolUse.id,
503+
content: JSON.stringify(resultContent),
504+
})
505+
}
473506

507+
// Add ONE assistant message with ALL tool_use blocks
508+
if (toolUseBlocks.length > 0) {
474509
currentMessages.push({
475510
role: 'assistant',
476-
content: [
477-
{
478-
type: 'tool_use',
479-
id: toolUseId,
480-
name: toolName,
481-
input: toolArgs,
482-
} as any,
483-
],
511+
content: toolUseBlocks as unknown as Anthropic.Messages.ContentBlock[],
484512
})
513+
}
485514

515+
// Add ONE user message with ALL tool_result blocks
516+
if (toolResultBlocks.length > 0) {
486517
currentMessages.push({
487518
role: 'user',
488-
content: [
489-
{
490-
type: 'tool_result',
491-
tool_use_id: toolUseId,
492-
content: JSON.stringify(resultContent),
493-
} as any,
494-
],
519+
content: toolResultBlocks as unknown as Anthropic.Messages.ContentBlockParam[],
495520
})
496521
}
497522

@@ -777,6 +802,8 @@ export const anthropicProvider: ProviderConfig = {
777802
const toolCallStartTime = Date.now()
778803
const toolName = toolUse.name
779804
const toolArgs = toolUse.input as Record<string, any>
805+
// Preserve the original tool_use ID from Claude's response
806+
const toolUseId = toolUse.id
780807

781808
try {
782809
const tool = request.tools?.find((t) => t.id === toolName)
@@ -787,6 +814,7 @@ export const anthropicProvider: ProviderConfig = {
787814
const toolCallEndTime = Date.now()
788815

789816
return {
817+
toolUseId,
790818
toolName,
791819
toolArgs,
792820
toolParams,
@@ -800,6 +828,7 @@ export const anthropicProvider: ProviderConfig = {
800828
logger.error('Error processing tool call:', { error, toolName })
801829

802830
return {
831+
toolUseId,
803832
toolName,
804833
toolArgs,
805834
toolParams: {},
@@ -817,11 +846,32 @@ export const anthropicProvider: ProviderConfig = {
817846

818847
const executionResults = await Promise.allSettled(toolExecutionPromises)
819848

849+
// Collect all tool_use and tool_result blocks for batching
850+
const toolUseBlocks: Array<{
851+
type: 'tool_use'
852+
id: string
853+
name: string
854+
input: Record<string, unknown>
855+
}> = []
856+
const toolResultBlocks: Array<{
857+
type: 'tool_result'
858+
tool_use_id: string
859+
content: string
860+
}> = []
861+
820862
for (const settledResult of executionResults) {
821863
if (settledResult.status === 'rejected' || !settledResult.value) continue
822864

823-
const { toolName, toolArgs, toolParams, result, startTime, endTime, duration } =
824-
settledResult.value
865+
const {
866+
toolUseId,
867+
toolName,
868+
toolArgs,
869+
toolParams,
870+
result,
871+
startTime,
872+
endTime,
873+
duration,
874+
} = settledResult.value
825875

826876
timeSegments.push({
827877
type: 'tool',
@@ -831,7 +881,7 @@ export const anthropicProvider: ProviderConfig = {
831881
duration: duration,
832882
})
833883

834-
let resultContent: any
884+
let resultContent: unknown
835885
if (result.success) {
836886
toolResults.push(result.output)
837887
resultContent = result.output
@@ -853,29 +903,34 @@ export const anthropicProvider: ProviderConfig = {
853903
success: result.success,
854904
})
855905

856-
const toolUseId = generateToolUseId(toolName)
906+
// Add to batched arrays using the ORIGINAL ID from Claude's response
907+
toolUseBlocks.push({
908+
type: 'tool_use',
909+
id: toolUseId,
910+
name: toolName,
911+
input: toolArgs,
912+
})
913+
914+
toolResultBlocks.push({
915+
type: 'tool_result',
916+
tool_use_id: toolUseId,
917+
content: JSON.stringify(resultContent),
918+
})
919+
}
857920

921+
// Add ONE assistant message with ALL tool_use blocks
922+
if (toolUseBlocks.length > 0) {
858923
currentMessages.push({
859924
role: 'assistant',
860-
content: [
861-
{
862-
type: 'tool_use',
863-
id: toolUseId,
864-
name: toolName,
865-
input: toolArgs,
866-
} as any,
867-
],
925+
content: toolUseBlocks as unknown as Anthropic.Messages.ContentBlock[],
868926
})
927+
}
869928

929+
// Add ONE user message with ALL tool_result blocks
930+
if (toolResultBlocks.length > 0) {
870931
currentMessages.push({
871932
role: 'user',
872-
content: [
873-
{
874-
type: 'tool_result',
875-
tool_use_id: toolUseId,
876-
content: JSON.stringify(resultContent),
877-
} as any,
878-
],
933+
content: toolResultBlocks as unknown as Anthropic.Messages.ContentBlockParam[],
879934
})
880935
}
881936

@@ -1061,7 +1116,7 @@ export const anthropicProvider: ProviderConfig = {
10611116
startTime: tc.startTime,
10621117
endTime: tc.endTime,
10631118
duration: tc.duration,
1064-
result: tc.result,
1119+
result: tc.result as Record<string, unknown> | undefined,
10651120
}))
10661121
: undefined,
10671122
toolResults: toolResults.length > 0 ? toolResults : undefined,

apps/sim/providers/bedrock/utils.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,17 @@ export function checkForForcedToolUsage(
6767
return null
6868
}
6969

70+
/**
71+
* Generates a unique tool use ID for Bedrock.
72+
* AWS Bedrock requires toolUseId to be 1-64 characters, pattern [a-zA-Z0-9_-]+
73+
*/
7074
export function generateToolUseId(toolName: string): string {
71-
return `${toolName}-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`
75+
const timestamp = Date.now().toString(36) // Base36 timestamp (8 chars)
76+
const random = Math.random().toString(36).substring(2, 7) // 5 random chars
77+
const suffix = `-${timestamp}-${random}` // ~15 chars
78+
const maxNameLength = 64 - suffix.length
79+
const truncatedName = toolName.substring(0, maxNameLength).replace(/[^a-zA-Z0-9_-]/g, '_')
80+
return `${truncatedName}${suffix}`
7281
}
7382

7483
/**

apps/sim/providers/deepseek/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ export const deepseekProvider: ProviderConfig = {
7676
: undefined
7777

7878
const payload: any = {
79-
model: 'deepseek-chat',
79+
model: request.model,
8080
messages: allMessages,
8181
}
8282

0 commit comments

Comments
 (0)