From d01516c120fa201f8a34ebc839fe7e6766c3f3f3 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Mon, 7 Sep 2026 22:52:24 +0800 Subject: [PATCH] fix(codex): explicitly default function tool strict to false - Forward `strict` as `false` when omitted in function tools to prevent upstream Responses API from defaulting it to `true`. Closes: #5555 --- .../chat-completions/codex_openai_request.go | 4 ++ .../codex_openai_request_test.go | 53 +++++++++++++++++++ 2 files changed, 57 insertions(+) diff --git a/internal/translator/codex/openai/chat-completions/codex_openai_request.go b/internal/translator/codex/openai/chat-completions/codex_openai_request.go index 307df55d4..126407a93 100644 --- a/internal/translator/codex/openai/chat-completions/codex_openai_request.go +++ b/internal/translator/codex/openai/chat-completions/codex_openai_request.go @@ -467,6 +467,10 @@ func ConvertOpenAIRequestToCodex(modelName string, inputRawJSON []byte, stream b } if v := fn.Get("strict"); v.Exists() { item, _ = sjson.SetBytes(item, "strict", v.Value()) + } else { + // Chat Completions defaults strict to false while the Responses API + // defaults it to true, so an omitted value must be forwarded explicitly. + item, _ = sjson.SetBytes(item, "strict", false) } } toolItems = append(toolItems, item) diff --git a/internal/translator/codex/openai/chat-completions/codex_openai_request_test.go b/internal/translator/codex/openai/chat-completions/codex_openai_request_test.go index 6d494614b..2f3d347fb 100644 --- a/internal/translator/codex/openai/chat-completions/codex_openai_request_test.go +++ b/internal/translator/codex/openai/chat-completions/codex_openai_request_test.go @@ -1404,3 +1404,56 @@ func TestToolsDefinitionTranslated(t *testing.T) { t.Errorf("tool 'search' not found in output tools: %s", gjson.Get(result, "tools").Raw) } } + +func TestFunctionToolStrictDefaultsToFalse(t *testing.T) { + input := []byte(`{ + "model": "gpt-5.6-sol", + "messages": [ + {"role": "user", "content": "Hi"} + ], + "tools": [ + { + "type": "function", + "function": { + "name": "omitted", + "parameters": {"type": "object", "properties": {"query": {"type": "string"}}} + } + }, + { + "type": "function", + "function": { + "name": "explicit_true", + "strict": true, + "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"], "additionalProperties": false} + } + }, + { + "type": "function", + "function": { + "name": "explicit_false", + "strict": false, + "parameters": {"type": "object", "properties": {"query": {"type": "string"}}} + } + } + ] + }`) + + out := ConvertOpenAIRequestToCodex("gpt-5.6-sol", input, true) + tools := gjson.GetBytes(out, "tools").Array() + if len(tools) != 3 { + t.Fatalf("expected 3 tools, got %d: %s", len(tools), gjson.GetBytes(out, "tools").Raw) + } + + expected := map[string]bool{"omitted": false, "explicit_true": true, "explicit_false": false} + for _, tool := range tools { + name := tool.Get("name").String() + strict := tool.Get("strict") + if !strict.Exists() { + t.Errorf("tool %q: strict missing in output: %s", name, tool.Raw) + continue + } + if strict.Bool() != expected[name] { + t.Errorf("tool %q: strict = %v, want %v", name, strict.Bool(), expected[name]) + } + } +}