mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-07 16:48:45 +08:00
fix(openai): strip invalid reasoning item IDs in API key passthrough
OpenAI upstream requires reasoning input item ids to begin with 'rs' and
rejects replayed item_* ids with 400 ("Expected an ID that begins with
'rs'."). shouldStripOpenAIResponsesInputItemID handled message (msg) and
tool call (fc) ids but not reasoning items, so long Codex sessions restored
through the OpenAI API key passthrough path failed with a converted 502.
Strip non-rs reasoning ids while preserving valid rs_* ids, mirroring the
existing message/tool-call handling. Fixes #5410.
This commit is contained in:
@@ -63,6 +63,72 @@ func TestOpenAIGatewayService_APIKeyPassthrough_StripsInvalidInputItemIDs(t *tes
|
||||
require.Equal(t, "item_unconstrained", gjson.GetBytes(forwarded, "input.5.id").String())
|
||||
}
|
||||
|
||||
// TestOpenAIGatewayService_APIKeyPassthrough_StripsInvalidReasoningItemIDs
|
||||
// verifies that reasoning items with a non-rs id (e.g. item_*) are stripped
|
||||
// before forwarding. OpenAI upstream requires reasoning ids to begin with
|
||||
// "rs" and rejects item_* with 400:
|
||||
// "Expected an ID that begins with 'rs'." (#5410)
|
||||
func TestOpenAIGatewayService_APIKeyPassthrough_StripsInvalidReasoningItemIDs(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
upstream := &httpUpstreamRecorder{resp: &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
Header: http.Header{"Content-Type": []string{"application/json"}},
|
||||
Body: io.NopCloser(strings.NewReader(
|
||||
`{"id":"resp_test","model":"gpt-5.6-sol","output":[],"usage":{"input_tokens":1,"output_tokens":1,"total_tokens":2}}`,
|
||||
)),
|
||||
}}
|
||||
svc := newOpenAIImageGenerationControlTestService(upstream)
|
||||
c, _ := newOpenAIImageGenerationControlTestContext(true, "codex_cli_rs/0.144.1")
|
||||
account := newOpenAIImageGenerationControlTestAccount()
|
||||
account.Extra = map[string]any{"openai_passthrough": true}
|
||||
|
||||
body := []byte(`{
|
||||
"model":"gpt-5.6-sol",
|
||||
"stream":false,
|
||||
"input":[
|
||||
{"type":"reasoning","id":"item_bad_reasoning","summary":[]},
|
||||
{"type":"reasoning","id":"rs_valid","summary":[]},
|
||||
{"type":"message","id":"msg_valid","role":"user","content":[{"type":"input_text","text":"continue"}]}
|
||||
]
|
||||
}`)
|
||||
|
||||
result, err := svc.Forward(context.Background(), c, account, body)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, result)
|
||||
require.NotNil(t, upstream.lastReq)
|
||||
|
||||
forwarded := upstream.lastBody
|
||||
require.False(t, gjson.GetBytes(forwarded, "input.0.id").Exists(),
|
||||
"item_* id should be stripped from reasoning")
|
||||
require.Equal(t, "rs_valid", gjson.GetBytes(forwarded, "input.1.id").String(),
|
||||
"valid rs* id must be preserved")
|
||||
require.Equal(t, "msg_valid", gjson.GetBytes(forwarded, "input.2.id").String())
|
||||
}
|
||||
|
||||
func TestShouldStripOpenAIResponsesInputItemID_Reasoning(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
itemType string
|
||||
id string
|
||||
want bool
|
||||
}{
|
||||
{"reasoning item_* id", "reasoning", "item_bad_reasoning", true},
|
||||
{"reasoning rs id", "reasoning", "rs_abc123", false},
|
||||
{"reasoning empty id", "reasoning", "", false},
|
||||
{"message msg id", "message", "msg_abc", false},
|
||||
{"message item id", "message", "item_x", true},
|
||||
{"function_call fc id", "function_call", "fc_abc", false},
|
||||
{"function_call item id", "function_call", "item_x", true},
|
||||
{"unconstrained type", "web_search_call", "ws_001", false},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
require.Equal(t, tc.want, shouldStripOpenAIResponsesInputItemID(tc.itemType, tc.id))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeOpenAIResponsesInputItemIDs_AllocationGrowthIsLinear(t *testing.T) {
|
||||
makeBody := func(itemCount int) []byte {
|
||||
items := make([]string, itemCount)
|
||||
|
||||
@@ -17,6 +17,9 @@ func shouldStripOpenAIResponsesInputItemID(itemType, id string) bool {
|
||||
if itemType == "message" {
|
||||
return !strings.HasPrefix(id, "msg")
|
||||
}
|
||||
if itemType == "reasoning" {
|
||||
return !strings.HasPrefix(id, "rs")
|
||||
}
|
||||
if isCodexToolCallInputType(itemType) {
|
||||
return !strings.HasPrefix(id, "fc")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user