diff --git a/packages/llm/src/protocols/openai-chat.ts b/packages/llm/src/protocols/openai-chat.ts index 470a1473c40b..4b00d05ab738 100644 --- a/packages/llm/src/protocols/openai-chat.ts +++ b/packages/llm/src/protocols/openai-chat.ts @@ -185,8 +185,13 @@ const lowerToolCall = (part: ToolCallPart): OpenAIChatAssistantToolCall => ({ }, }) -const openAICompatibleReasoningContent = (native: unknown) => - isRecord(native) && typeof native.reasoning_content === "string" ? native.reasoning_content : undefined +const openAICompatibleReasoningContent = (native: unknown) => { + if (!isRecord(native)) return undefined + if (!isRecord(native["providerOptions"])) return undefined + if (!isRecord(native["providerOptions"]["openaiCompatible"])) return undefined + const reasoning = native["providerOptions"]["openaiCompatible"]["reasoning_content"] + return typeof reasoning === "string" ? reasoning : undefined +} const lowerUserMessage = Effect.fn("OpenAIChat.lowerUserMessage")(function* (message: OpenAIChatRequestMessage) { const content: TextPart[] = [] @@ -219,7 +224,7 @@ const lowerAssistantMessage = Effect.fn("OpenAIChat.lowerAssistantMessage")(func role: "assistant" as const, content: content.length === 0 ? null : ProviderShared.joinText(content), tool_calls: toolCalls.length === 0 ? undefined : toolCalls, - reasoning_content: openAICompatibleReasoningContent(message.native?.openaiCompatible), + reasoning_content: openAICompatibleReasoningContent(message.native), } }) diff --git a/packages/llm/test/provider/openai-chat.test.ts b/packages/llm/test/provider/openai-chat.test.ts index 4303a69ffa5c..98a62a58956a 100644 --- a/packages/llm/test/provider/openai-chat.test.ts +++ b/packages/llm/test/provider/openai-chat.test.ts @@ -179,6 +179,38 @@ describe("OpenAI Chat route", () => { }), ) + it.effect("prepares assistant tool-call message with reasoning_content from native providerOptions", () => + Effect.gen(function* () { + const prepared = yield* LLMClient.prepare( + LLM.request({ + id: "req_reasoning_tool_call", + model, + messages: [ + Message.user("What is the weather?"), + new Message({ + role: "assistant", + content: [ToolCallPart.make({ id: "call_1", name: "lookup", input: { query: "weather" } })], + native: { providerOptions: { openaiCompatible: { reasoning_content: "thinking about weather" } } }, + }), + ], + }), + ) + + expect(prepared.body.messages[1]).toEqual({ + role: "assistant", + content: null, + tool_calls: [ + { + id: "call_1", + type: "function", + function: { name: "lookup", arguments: encodeJson({ query: "weather" }) }, + }, + ], + reasoning_content: "thinking about weather", + }) + }), + ) + it.effect("rejects unsupported user media content", () => Effect.gen(function* () { const error = yield* LLMClient.prepare(