@@ -65,6 +65,12 @@ const FIXTURES = {
6565 `data: {"choices":[{"finish_reason":"tool_calls","index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{\\"code\\":\\"1 + 1\\"}","name":"project_eval"},"id":"call_MHw3RDhmT1J5Z3B6WlhpVjlveTc","index":0,"type":"function"}],"reasoning_opaque":"ytGNWFf2doK38peANDvm7whkLPKrd+Fv6/k34zEPBF6Qwitj4bTZT0FBXleydLb6"}}],"created":1766068644,"id":"oBFEaafzD9DVlOoPkY3l4Qs","usage":{"completion_tokens":12,"prompt_tokens":8677,"prompt_tokens_details":{"cached_tokens":3692},"total_tokens":8768,"reasoning_tokens":79},"model":"gemini-3-pro-preview"}` ,
6666 `data: [DONE]` ,
6767 ] ,
68+
69+ reasoningOpaqueWithToolCallsNoReasoningText : [
70+ `data: {"choices":[{"index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{}","name":"read_file"},"id":"call_reasoning_only","index":0,"type":"function"}],"reasoning_opaque":"opaque-xyz"}}],"created":1769917420,"id":"opaque-only","usage":{"completion_tokens":0,"prompt_tokens":0,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":0,"reasoning_tokens":0},"model":"gemini-3-flash-preview"}` ,
71+ `data: {"choices":[{"finish_reason":"tool_calls","index":0,"delta":{"content":null,"role":"assistant","tool_calls":[{"function":{"arguments":"{}","name":"read_file"},"id":"call_reasoning_only_2","index":1,"type":"function"}]}}],"created":1769917420,"id":"opaque-only","usage":{"completion_tokens":12,"prompt_tokens":123,"prompt_tokens_details":{"cached_tokens":0},"total_tokens":135,"reasoning_tokens":0},"model":"gemini-3-flash-preview"}` ,
72+ `data: [DONE]` ,
73+ ] ,
6874}
6975
7076function createMockFetch ( chunks : string [ ] ) {
@@ -447,6 +453,35 @@ describe("doStream", () => {
447453 } )
448454 } )
449455
456+ test ( "should attach reasoning_opaque to tool calls without reasoning_text" , async ( ) => {
457+ const mockFetch = createMockFetch ( FIXTURES . reasoningOpaqueWithToolCallsNoReasoningText )
458+ const model = createModel ( mockFetch )
459+
460+ const { stream } = await model . doStream ( {
461+ prompt : TEST_PROMPT ,
462+ includeRawChunks : false ,
463+ } )
464+
465+ const parts = await convertReadableStreamToArray ( stream )
466+ const reasoningParts = parts . filter (
467+ ( p ) => p . type === "reasoning-start" || p . type === "reasoning-delta" || p . type === "reasoning-end" ,
468+ )
469+
470+ expect ( reasoningParts ) . toHaveLength ( 0 )
471+
472+ const toolCall = parts . find ( ( p ) => p . type === "tool-call" && p . toolCallId === "call_reasoning_only" )
473+ expect ( toolCall ) . toMatchObject ( {
474+ type : "tool-call" ,
475+ toolCallId : "call_reasoning_only" ,
476+ toolName : "read_file" ,
477+ providerMetadata : {
478+ copilot : {
479+ reasoningOpaque : "opaque-xyz" ,
480+ } ,
481+ } ,
482+ } )
483+ } )
484+
450485 test ( "should include response metadata from first chunk" , async ( ) => {
451486 const mockFetch = createMockFetch ( FIXTURES . basicText )
452487 const model = createModel ( mockFetch )
0 commit comments