@@ -5327,17 +5327,8 @@ describe("openai transport stream", () => {
53275327 } ) ;
53285328} ) ;
53295329
5330- describe ( "buildOpenAICompletionsParams strips response-only reasoning fields" , ( ) => {
5331- // OpenRouter and other OpenAI-Completions providers echo `reasoning_details`
5332- // (array) and `reasoning_content` (string) in response choices to expose the
5333- // model's chain of thought. If those fields leak back into a follow-up
5334- // request, OpenRouter rejects the call with HTTP 500 ("Internal Server
5335- // Error"). buildOpenAICompletionsParams must scrub them before the wire.
5336- // Repro recipe (verified against live OpenRouter):
5337- // POST /chat/completions with messages[*].reasoning_details: "..." => 500
5338- // Same body without that key => 200
5339-
5340- const baseModel = {
5330+ describe ( "buildOpenAICompletionsParams sanitizes reasoning replay fields" , ( ) => {
5331+ const openRouterModel = {
53415332 id : "deepseek/deepseek-v4-flash" ,
53425333 name : "DeepSeek v4 Flash" ,
53435334 api : "openai-completions" ,
@@ -5350,6 +5341,19 @@ describe("buildOpenAICompletionsParams strips response-only reasoning fields", (
53505341 maxTokens : 8192 ,
53515342 } satisfies Model < "openai-completions" > ;
53525343
5344+ const openAIModel = {
5345+ id : "gpt-5.4-mini" ,
5346+ name : "GPT-5.4 Mini" ,
5347+ api : "openai-completions" ,
5348+ provider : "openai" ,
5349+ baseUrl : "https://api.openai.com/v1" ,
5350+ reasoning : true ,
5351+ input : [ "text" ] ,
5352+ cost : { input : 0 , output : 0 , cacheRead : 0 , cacheWrite : 0 } ,
5353+ contextWindow : 200_000 ,
5354+ maxTokens : 8192 ,
5355+ } satisfies Model < "openai-completions" > ;
5356+
53535357 function getAssistantMessage ( params : { messages : unknown } ) {
53545358 expect ( Array . isArray ( params . messages ) ) . toBe ( true ) ;
53555359 const list = params . messages as Array < Record < string , unknown > > ;
@@ -5358,13 +5362,80 @@ describe("buildOpenAICompletionsParams strips response-only reasoning fields", (
53585362 return assistant as Record < string , unknown > ;
53595363 }
53605364
5361- it ( "removes reasoning_details, reasoning_content, and reasoning from assistant replay" , ( ) => {
5365+ function buildReplayParams ( model : Model < "openai-completions" > , thinkingSignature : string ) {
5366+ return buildOpenAICompletionsParams (
5367+ model ,
5368+ {
5369+ systemPrompt : "system" ,
5370+ messages : [
5371+ { role : "user" , content : "hello" } ,
5372+ {
5373+ role : "assistant" ,
5374+ provider : model . provider ,
5375+ api : model . api ,
5376+ model : model . id ,
5377+ stopReason : "stop" ,
5378+ timestamp : 0 ,
5379+ content : [
5380+ {
5381+ type : "thinking" ,
5382+ thinking : "Need to answer politely." ,
5383+ thinkingSignature,
5384+ } ,
5385+ { type : "text" , text : "Hello!" } ,
5386+ ] ,
5387+ } ,
5388+ { role : "user" , content : "again" } ,
5389+ ] ,
5390+ tools : [ ] ,
5391+ } as never ,
5392+ undefined ,
5393+ ) as { messages : unknown } ;
5394+ }
5395+
5396+ it . each ( [ "reasoning_details" , "reasoning_content" , "reasoning" , "reasoning_text" ] ) (
5397+ "strips %s from stock OpenAI Chat Completions assistant replay" ,
5398+ ( thinkingSignature ) => {
5399+ const assistant = getAssistantMessage ( buildReplayParams ( openAIModel , thinkingSignature ) ) ;
5400+
5401+ expect ( assistant ) . not . toHaveProperty ( "reasoning_details" ) ;
5402+ expect ( assistant ) . not . toHaveProperty ( "reasoning_content" ) ;
5403+ expect ( assistant ) . not . toHaveProperty ( "reasoning" ) ;
5404+ expect ( assistant ) . not . toHaveProperty ( "reasoning_text" ) ;
5405+ } ,
5406+ ) ;
5407+
5408+ it ( "normalizes OpenRouter string reasoning_details to reasoning" , ( ) => {
5409+ const assistant = getAssistantMessage ( buildReplayParams ( openRouterModel , "reasoning_details" ) ) ;
5410+
5411+ expect ( assistant ) . not . toHaveProperty ( "reasoning_details" ) ;
5412+ expect ( assistant . reasoning ) . toBe ( "Need to answer politely." ) ;
5413+ } ) ;
5414+
5415+ it . each ( [ "reasoning" , "reasoning_content" ] ) (
5416+ "preserves OpenRouter %s string reasoning replay" ,
5417+ ( thinkingSignature ) => {
5418+ const assistant = getAssistantMessage ( buildReplayParams ( openRouterModel , thinkingSignature ) ) ;
5419+
5420+ expect ( assistant [ thinkingSignature ] ) . toBe ( "Need to answer politely." ) ;
5421+ } ,
5422+ ) ;
5423+
5424+ it ( "normalizes OpenRouter reasoning_text to reasoning" , ( ) => {
5425+ const assistant = getAssistantMessage ( buildReplayParams ( openRouterModel , "reasoning_text" ) ) ;
5426+
5427+ expect ( assistant ) . not . toHaveProperty ( "reasoning_text" ) ;
5428+ expect ( assistant . reasoning ) . toBe ( "Need to answer politely." ) ;
5429+ } ) ;
5430+
5431+ it ( "preserves OpenRouter array reasoning_details from tool-call signatures" , ( ) => {
5432+ const reasoningDetail = { type : "reasoning.encrypted" , id : "rs_1" , data : "ciphertext" } ;
53625433 const params = buildOpenAICompletionsParams (
5363- baseModel ,
5434+ openRouterModel ,
53645435 {
53655436 systemPrompt : "system" ,
53665437 messages : [
5367- { role : "user" , content : "你好 " } ,
5438+ { role : "user" , content : "lookup " } ,
53685439 {
53695440 role : "assistant" ,
53705441 provider : "openrouter" ,
@@ -5374,23 +5445,30 @@ describe("buildOpenAICompletionsParams strips response-only reasoning fields", (
53745445 timestamp : 0 ,
53755446 content : [
53765447 {
5377- type : "thinking" ,
5378- thinking : "User said hi, I should respond politely." ,
5379- thinkingSignature : "considering" ,
5448+ type : "toolCall" ,
5449+ id : "call_1" ,
5450+ name : "lookup" ,
5451+ arguments : { query : "weather" } ,
5452+ thoughtSignature : JSON . stringify ( reasoningDetail ) ,
53805453 } ,
5381- { type : "text" , text : "Hello!" } ,
53825454 ] ,
53835455 } ,
5384- { role : "user" , content : "再来一个" } ,
5456+ {
5457+ role : "toolResult" ,
5458+ toolCallId : "call_1" ,
5459+ toolName : "lookup" ,
5460+ content : [ { type : "text" , text : "sunny" } ] ,
5461+ isError : false ,
5462+ timestamp : 1 ,
5463+ } ,
5464+ { role : "user" , content : "answer" } ,
53855465 ] ,
53865466 tools : [ ] ,
53875467 } as never ,
53885468 undefined ,
53895469 ) as { messages : unknown } ;
53905470
53915471 const assistant = getAssistantMessage ( params ) ;
5392- expect ( assistant ) . not . toHaveProperty ( "reasoning_details" ) ;
5393- expect ( assistant ) . not . toHaveProperty ( "reasoning_content" ) ;
5394- expect ( assistant ) . not . toHaveProperty ( "reasoning" ) ;
5472+ expect ( assistant . reasoning_details ) . toEqual ( [ reasoningDetail ] ) ;
53955473 } ) ;
53965474} ) ;
0 commit comments