diff --git a/core/config/types.ts b/core/config/types.ts index d9f58aca6e2..9e72ec1ce01 100644 --- a/core/config/types.ts +++ b/core/config/types.ts @@ -302,6 +302,7 @@ declare global { name: string; arguments: string; }; + responsesOutputItemId?: string; } export interface ToolCallDelta { @@ -311,6 +312,7 @@ declare global { name?: string; arguments?: string; }; + responsesOutputItemId?: string; } export interface ToolResultChatMessage { diff --git a/core/index.d.ts b/core/index.d.ts index f31b62ed7d6..02577388f65 100644 --- a/core/index.d.ts +++ b/core/index.d.ts @@ -348,6 +348,7 @@ export interface ToolCall { name: string; arguments: string; }; + responsesOutputItemId?: string; } export interface ToolCallDelta { @@ -357,6 +358,7 @@ export interface ToolCallDelta { name?: string; arguments?: string; }; + responsesOutputItemId?: string; } export interface ToolResultChatMessage { diff --git a/core/llm/openaiTypeConverters.ts b/core/llm/openaiTypeConverters.ts index a7e3d6bacd8..48119a64051 100644 --- a/core/llm/openaiTypeConverters.ts +++ b/core/llm/openaiTypeConverters.ts @@ -774,6 +774,7 @@ function toResponseInputContentList( return list; } +// eslint-disable-next-line complexity export function toResponsesInput(messages: ChatMessage[]): ResponseInput { const input: ResponseInput = []; @@ -816,22 +817,34 @@ export function toResponsesInput(messages: ChatMessage[]): ResponseInput { const respId = msg.metadata?.responsesOutputItemId as | string | undefined; + const toolCalls = msg.toolCalls as ToolCallDelta[] | undefined; - if (respId && Array.isArray(toolCalls) && toolCalls.length > 0) { - // Emit full function_call output item - const tc = toolCalls[0]; - const name = tc?.function?.name as string | undefined; - const args = tc?.function?.arguments as string | undefined; - const call_id = tc?.id as string | undefined; - const functionCallItem: ResponseFunctionToolCall = { - id: respId, - type: "function_call", - name: name || "", - arguments: typeof args === "string" ? args : "{}", - call_id: call_id || respId, - }; - input.push(functionCallItem); + if (Array.isArray(toolCalls) && toolCalls.length > 0) { + // Emit one function_call item per recorded tool call, + // prefer per-tool responsesOutputItemId when available + for (const tc of toolCalls) { + const name = tc?.function?.name as string | undefined; + const args = tc?.function?.arguments as string | undefined; + const tcRespId = (tc as any).responsesOutputItemId as + | string + | undefined; + const callId = tc?.id as string | undefined; + const rawItemId = tcRespId || respId || callId; + const itemId = rawItemId + ? rawItemId.startsWith("fc") + ? rawItemId + : `fc_${rawItemId}` + : undefined; + const functionCallItem: ResponseFunctionToolCall = { + id: itemId, + type: "function_call", + name: name || "", + arguments: typeof args === "string" ? args : "{}", + call_id: callId || tcRespId || respId || "", + }; + input.push(functionCallItem); + } } else if (respId) { // Emit full assistant output message item const outputMessageItem: ResponseOutputMessage = { diff --git a/gui/src/redux/slices/sessionSlice.ts b/gui/src/redux/slices/sessionSlice.ts index 3656af0c185..f7697e60dfc 100644 --- a/gui/src/redux/slices/sessionSlice.ts +++ b/gui/src/redux/slices/sessionSlice.ts @@ -87,9 +87,12 @@ export function handleToolCallsInMessage( // Initialize tool call states for each filtered tool call in the message // Each tool call gets its own state to track generation/execution progress - lastItem.toolCallStates = filteredToolCalls.map((toolCallDelta) => - addToolCallDeltaToState(toolCallDelta, undefined), - ); + lastItem.toolCallStates = filteredToolCalls.map((toolCallDelta) => { + const state = addToolCallDeltaToState(toolCallDelta, undefined); + state.toolCall.responsesOutputItemId = + (message.metadata?.responsesOutputItemId as string) ?? undefined; + return state; + }); // Update the message's toolCalls array to reflect the processed tool calls // We can safely cast because we verified the role above @@ -116,6 +119,7 @@ export function handleToolCallsInMessage( function applyToolCallDelta( toolCallDelta: ToolCallDelta, toolCallStates: ToolCallState[], + responsesOutputItemId: string, ): void { // Find existing state by matching toolCallId - this ensures we update // the correct tool call even when multiple tool calls are being streamed @@ -150,6 +154,7 @@ function applyToolCallDelta( toolCallStates[existingStateIndex] = updatedState; } else { // Add new tool call state for a newly discovered tool call + updatedState.toolCall.responsesOutputItemId = responsesOutputItemId; toolCallStates.push(updatedState); } } @@ -181,7 +186,11 @@ export function handleStreamingToolCallUpdates( // Process each filtered tool call delta, matching by ID to update the correct state filteredToolCalls.forEach((toolCallDelta) => { - applyToolCallDelta(toolCallDelta, updatedToolCallStates); + applyToolCallDelta( + toolCallDelta, + updatedToolCallStates, + message.metadata?.responsesOutputItemId as string, + ); }); // Replace the entire tool call states array with the updated version diff --git a/gui/src/util/toolCallState.ts b/gui/src/util/toolCallState.ts index c7b9bcbd7df..5c990f20fd5 100644 --- a/gui/src/util/toolCallState.ts +++ b/gui/src/util/toolCallState.ts @@ -63,6 +63,7 @@ export function addToolCallDeltaToState( name: mergedName, arguments: mergedArgs, }, + responsesOutputItemId: currentCall?.responsesOutputItemId, }, toolCallId: callId, parsedArgs,