mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-10-19 16:17:56 +00:00
fix(codex): preserve allowed_tools selection constraints
Keep allowed_tools policies intact for upstream validation and rewrite their function references alongside reserved tool-name aliases.
This commit is contained in:
@@ -115,6 +115,9 @@ func collectOpenAIResponsesToolNameFields(reqBody map[string]any) []codexToolNam
|
||||
}
|
||||
}
|
||||
if choice, ok := reqBody["tool_choice"].(map[string]any); ok {
|
||||
if strings.EqualFold(strings.TrimSpace(firstNonEmptyString(choice["type"])), "allowed_tools") {
|
||||
collectTools(choice["tools"])
|
||||
}
|
||||
if strings.EqualFold(strings.TrimSpace(firstNonEmptyString(choice["type"])), "function") {
|
||||
appendName(choice, "name")
|
||||
if function, ok := choice["function"].(map[string]any); ok {
|
||||
|
||||
@@ -357,6 +357,12 @@ func normalizeCodexToolChoice(reqBody map[string]any) bool {
|
||||
if choiceType == "" {
|
||||
return false
|
||||
}
|
||||
if choiceType == "allowed_tools" {
|
||||
// This is a selection policy, not a declared tool type. Preserve it for
|
||||
// upstream validation (including references to input.additional_tools);
|
||||
// falling back to auto would silently discard the caller's restriction.
|
||||
return false
|
||||
}
|
||||
modified := false
|
||||
if choiceType == "function" {
|
||||
name := strings.TrimSpace(firstNonEmptyString(choiceMap["name"]))
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -477,6 +478,74 @@ func TestApplyCodexOAuthTransform_StringifiesNonStringMessageContentText(t *test
|
||||
require.Equal(t, `["a","b"]`, part["text"])
|
||||
}
|
||||
|
||||
func TestApplyCodexOAuthTransform_PreservesAllowedTools(t *testing.T) {
|
||||
for _, placement := range []string{"top_level", "additional_tools"} {
|
||||
for _, mode := range []string{"auto", "required"} {
|
||||
t.Run(placement+"/"+mode, func(t *testing.T) {
|
||||
decision := map[string]any{"type": "function", "name": "ProbeAccept"}
|
||||
choice := map[string]any{
|
||||
"type": "allowed_tools", "mode": mode,
|
||||
"tools": []any{map[string]any{"type": "function", "name": "ProbeAccept"}},
|
||||
}
|
||||
tools := []any{
|
||||
map[string]any{"type": "function", "name": "ProbeBase"},
|
||||
map[string]any{"type": "web_search"},
|
||||
map[string]any{"type": "image_generation"},
|
||||
}
|
||||
input := []any{map[string]any{"type": "message", "role": "user", "content": "probe"}}
|
||||
if placement == "top_level" {
|
||||
tools = append(tools, decision)
|
||||
} else {
|
||||
input = append(input, map[string]any{
|
||||
"type": "additional_tools", "role": "developer", "tools": []any{decision},
|
||||
})
|
||||
}
|
||||
reqBody := map[string]any{"tools": tools, "input": input, "tool_choice": choice}
|
||||
before, err := json.Marshal(reqBody)
|
||||
require.NoError(t, err)
|
||||
reqBody["model"] = "gpt-6-astra"
|
||||
result := applyCodexOAuthTransform(reqBody, true, false)
|
||||
require.NoError(t, result.Error)
|
||||
after, err := json.Marshal(map[string]any{
|
||||
"tools": reqBody["tools"], "input": reqBody["input"], "tool_choice": reqBody["tool_choice"],
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.JSONEq(t, string(before), string(after))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeCodexToolChoice_InvalidAllowedToolsNeverBecomesAuto(t *testing.T) {
|
||||
for _, choice := range []map[string]any{
|
||||
{"type": "allowed_tools"},
|
||||
{"type": "allowed_tools", "mode": "invalid", "tools": []any{}},
|
||||
{"type": "allowed_tools", "mode": "required", "tools": "invalid"},
|
||||
{"type": "allowed_tools", "mode": "required", "tools": []any{map[string]any{"type": "function", "name": "missing"}}},
|
||||
} {
|
||||
reqBody := map[string]any{"tool_choice": choice}
|
||||
require.False(t, normalizeCodexToolChoice(reqBody))
|
||||
// The upstream owns schema validation. A malformed restriction must never
|
||||
// silently become permission to call every supplied tool.
|
||||
require.Equal(t, choice, reqBody["tool_choice"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestApplyCodexOAuthTransform_AllowedToolsKeepsReservedNameReferences(t *testing.T) {
|
||||
declaration := map[string]any{"type": "function", "name": "python"}
|
||||
reference := map[string]any{"type": "function", "name": "python"}
|
||||
choice := map[string]any{"type": "allowed_tools", "mode": "required", "tools": []any{reference}}
|
||||
reqBody := map[string]any{
|
||||
"model": "gpt-6-astra", "tools": []any{declaration}, "tool_choice": choice,
|
||||
}
|
||||
result := applyCodexOAuthTransform(reqBody, true, false)
|
||||
require.NoError(t, result.Error)
|
||||
require.Equal(t, choice, reqBody["tool_choice"])
|
||||
require.Equal(t, codexPythonToolAlias, declaration["name"])
|
||||
require.Equal(t, codexPythonToolAlias, reference["name"])
|
||||
require.Equal(t, "python", result.ToolNameReverse[codexPythonToolAlias])
|
||||
}
|
||||
|
||||
func TestApplyCodexOAuthTransform_DowngradesUnknownToolChoice(t *testing.T) {
|
||||
reqBody := map[string]any{
|
||||
"model": "gpt-5.4",
|
||||
|
||||
Reference in New Issue
Block a user