fix(apicompat): 回程还原 namespace 子工具调用,修复 Codex MCP 工具 unsupported call

Codex 0.14x 将 MCP 工具声明为 namespace 工具,chat 桥去程摊平为
"<namespace>__<name>" function 工具后,回程仅原样回传平铺名的
function_call 项;codex 按 namespace+name 路由查不到该名字,所有
MCP 工具调用被判为 unsupported call。

- NamespaceToolNames 构建摊平名 →(namespace, 子工具名)反查表
  (摊平名超长带截断哈希,无法按字符串切分还原)
- 非流式/流式回程命中映射时还原为裸子工具名 + namespace 字段,
  ResponsesOutput 新增 Namespace 字段并同步 wire 层白名单
- 回退桥入口将映射与 CustomTools/ToolSearchDeclared 一并穿入

已在测试机经 codex exec + MCP server 端到端验证。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NWQyEgFKKbdve67G6qCoAU
This commit is contained in:
xlplbo
2026-07-10 17:38:17 +08:00
co-authored by Claude Fable 5
parent 18e26c127c
commit 7942338328
7 changed files with 296 additions and 23 deletions
@@ -64,6 +64,42 @@ func CustomToolNames(tools []ResponsesTool) map[string]bool {
return out
}
// NamespacedToolName 记录 namespace 子工具的原始归属(命名空间 + 裸子工具名)。
type NamespacedToolName struct {
Namespace string
Name string
}
// NamespaceToolNames 收集 Responses 请求中 namespace 子工具的摊平名 →(namespace,
// 子工具名)映射。chat 桥回程时需据此把模型对摊平工具的调用还原为带 namespace 字段
// 的 function_call 项:codex 按 namespace+name 路由,平铺名会被判为 unsupported
// call;摊平名超长时带截断哈希(见 flattenNamespaceToolName),无法按字符串切分还原。
func NamespaceToolNames(tools []ResponsesTool) map[string]NamespacedToolName {
var out map[string]NamespacedToolName
for _, tool := range tools {
if tool.Type != "namespace" || tool.Name == "" {
continue
}
children := tool.Tools
if len(children) == 0 {
children = tool.Children
}
for _, child := range children {
if child.Type != "function" || child.Name == "" {
continue
}
if out == nil {
out = make(map[string]NamespacedToolName)
}
out[flattenNamespaceToolName(tool.Name, child.Name)] = NamespacedToolName{
Namespace: tool.Name,
Name: child.Name,
}
}
}
return out
}
// HasToolSearchTool 判断 Responses 请求是否声明了 tool_search 服务端工具。chat 桥
// 回程时需据此把模型对代理工具的调用还原为 tool_search_call 项:codex 只在该项类型
// 且 execution=client 时执行 tool search,同名 function_call 会因 payload 不匹配
@@ -679,8 +715,9 @@ func extractCustomToolCallInput(arguments string) string {
// response into a Responses API response. customTools 是客户端请求中 custom 工具
// 的名字集合(见 CustomToolNames),命中的调用会还原为 custom_tool_call 项;
// toolSearch 表示客户端声明了 tool_search 工具(见 HasToolSearchTool),代理工具
// 的调用会还原为 tool_search_call 项。
func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model string, customTools map[string]bool, toolSearch bool) *ResponsesResponse {
// 的调用会还原为 tool_search_call 项;namespaceTools 是 namespace 子工具的摊平名
// 映射(见 NamespaceToolNames),命中的调用还原为带 namespace 字段的 function_call 项。
func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model string, customTools map[string]bool, toolSearch bool, namespaceTools map[string]NamespacedToolName) *ResponsesResponse {
id := ""
if resp != nil {
id = resp.ID
@@ -705,7 +742,7 @@ func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model str
if len(resp.Choices) > 0 {
choice := resp.Choices[0]
out.Output = chatMessageToResponsesOutput(choice.Message, customTools, toolSearch)
out.Output = chatMessageToResponsesOutput(choice.Message, customTools, toolSearch, namespaceTools)
if choice.FinishReason == "length" {
out.Status = "incomplete"
out.IncompleteDetails = &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
@@ -720,7 +757,7 @@ func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model str
return out
}
func chatMessageToResponsesOutput(message ChatMessage, customTools map[string]bool, toolSearch bool) []ResponsesOutput {
func chatMessageToResponsesOutput(message ChatMessage, customTools map[string]bool, toolSearch bool, namespaceTools map[string]NamespacedToolName) []ResponsesOutput {
var outputs []ResponsesOutput
if message.ReasoningContent != "" {
outputs = append(outputs, ResponsesOutput{
@@ -776,6 +813,18 @@ func chatMessageToResponsesOutput(message ChatMessage, customTools map[string]bo
})
continue
}
if ns, ok := namespaceTools[toolCall.Function.Name]; ok {
outputs = append(outputs, ResponsesOutput{
Type: "function_call",
ID: generateItemID(),
CallID: toolCall.ID,
Name: ns.Name,
Namespace: ns.Namespace,
Arguments: arguments,
Status: "completed",
})
continue
}
outputs = append(outputs, ResponsesOutput{
Type: "function_call",
ID: generateItemID(),
@@ -905,6 +954,11 @@ type ChatCompletionsToResponsesStreamState struct {
// 该项类型(且 execution=client)执行 tool search。
ToolSearchDeclared bool
// NamespaceTools 是 namespace 子工具的摊平名 → 原始归属映射(见
// NamespaceToolNames)。命中的调用还原为带 namespace 字段的 function_call 项,
// codex 按 namespace+name 路由。
NamespaceTools map[string]NamespacedToolName
// toolIsCustom 记录每个工具调用宣告时的类型判定,保证 added/done 事件的
// 项类型一致。
toolIsCustom map[int]bool
@@ -912,6 +966,9 @@ type ChatCompletionsToResponsesStreamState struct {
// toolIsToolSearch 记录工具调用是否判定为 tool_search 代理调用。
toolIsToolSearch map[int]bool
// toolNamespace 记录工具调用宣告时命中的 namespace 归属(见 NamespaceTools)。
toolNamespace map[int]NamespacedToolName
// toolAnnounced 记录 output_item.added 是否已发出。存在 custom 工具且名字
// 尚未到达时延迟宣告,待名字可判定类型后再补发(见 announceChatToolItem)。
toolAnnounced map[int]bool
@@ -931,6 +988,7 @@ func NewChatCompletionsToResponsesStreamState(model string) *ChatCompletionsToRe
ToolOutputIndex: make(map[int]int),
toolIsCustom: make(map[int]bool),
toolIsToolSearch: make(map[int]bool),
toolNamespace: make(map[int]NamespacedToolName),
toolAnnounced: make(map[int]bool),
}
}
@@ -1263,7 +1321,7 @@ func announceChatToolItem(
if state.toolAnnounced[idx] {
return nil
}
if !force && stored.Function.Name == "" && (len(state.CustomTools) > 0 || state.ToolSearchDeclared) {
if !force && stored.Function.Name == "" && (len(state.CustomTools) > 0 || state.ToolSearchDeclared || len(state.NamespaceTools) > 0) {
return nil
}
state.toolAnnounced[idx] = true
@@ -1278,14 +1336,22 @@ func announceChatToolItem(
if isToolSearch {
itemType = "tool_search_call"
}
// namespace 子工具的调用仍按 function_call 生命周期下发,但 added/done 项要
// 还原为裸子工具名 + namespace 字段(codex 按 namespace+name 路由)。
itemName, itemNamespace := stored.Function.Name, ""
if ns, ok := state.NamespaceTools[stored.Function.Name]; ok && !isCustom && !isToolSearch {
state.toolNamespace[idx] = ns
itemName, itemNamespace = ns.Name, ns.Namespace
}
events := []ResponsesStreamEvent{chatToResponsesEvent(state, "response.output_item.added", &ResponsesStreamEvent{
OutputIndex: state.ToolOutputIndex[idx],
Item: &ResponsesOutput{
Type: itemType,
ID: state.ToolItemIDs[idx],
CallID: stored.ID,
Name: stored.Function.Name,
Status: "in_progress",
Type: itemType,
ID: state.ToolItemIDs[idx],
CallID: stored.ID,
Name: itemName,
Namespace: itemNamespace,
Status: "in_progress",
},
})}
// 迟到宣告时补发已累积的参数增量(custom/tool_search 的输入收尾统一下发,不补发)。
@@ -1374,12 +1440,17 @@ func closeChatToolItems(state *ChatCompletionsToResponsesStreamState) []Response
}))
continue
}
// namespace 子工具调用在宣告时已记录归属,收尾项同样带还原名与 namespace。
name, namespace := toolCall.Function.Name, ""
if ns, ok := state.toolNamespace[i]; ok {
name, namespace = ns.Name, ns.Namespace
}
events = append(events,
chatToResponsesEvent(state, "response.function_call_arguments.done", &ResponsesStreamEvent{
OutputIndex: outputIndex,
ItemID: itemID,
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: name,
Arguments: arguments,
}),
chatToResponsesEvent(state, "response.output_item.done", &ResponsesStreamEvent{
@@ -1388,7 +1459,8 @@ func closeChatToolItems(state *ChatCompletionsToResponsesStreamState) []Response
Type: "function_call",
ID: itemID,
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: name,
Namespace: namespace,
Arguments: arguments,
Status: "completed",
},
@@ -1452,11 +1524,16 @@ func (state *ChatCompletionsToResponsesStreamState) chatOutput() []ResponsesOutp
})
continue
}
name, namespace := toolCall.Function.Name, ""
if ns, ok := state.toolNamespace[i]; ok {
name, namespace = ns.Name, ns.Namespace
}
outputs = append(outputs, ResponsesOutput{
Type: "function_call",
ID: generateItemID(),
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: name,
Namespace: namespace,
Arguments: arguments,
Status: "completed",
})
@@ -103,7 +103,7 @@ func TestChatCompletionsResponseToResponses_CustomToolCallOutputItem(t *testing.
}},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", map[string]bool{"exec": true}, false)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", map[string]bool{"exec": true}, false, nil)
require.Len(t, out.Output, 2)
assert.Equal(t, "custom_tool_call", out.Output[0].Type)
@@ -226,7 +226,7 @@ func TestChatCompletionsResponseToResponses_ToolSearchCallOutputItem(t *testing.
}},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, true)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, true, nil)
require.Len(t, out.Output, 1)
item := out.Output[0]
@@ -258,7 +258,7 @@ func TestChatCompletionsResponseToResponses_ToolSearchNotDeclaredKeepsFunctionCa
}
// 客户端未声明 type=tool_search 时,同名普通 function 工具不受影响。
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, false)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, false, nil)
require.Len(t, out.Output, 1)
assert.Equal(t, "function_call", out.Output[0].Type)
}
@@ -506,6 +506,190 @@ func TestResponsesEventToSSE_CustomToolCallItemCarriesAllFields(t *testing.T) {
assert.Contains(t, sse, `"type":"custom_tool_call"`)
}
func TestNamespaceToolNames_MapsFlattenedNames(t *testing.T) {
tools := []ResponsesTool{
{Type: "namespace", Name: "gmail", Tools: []ResponsesTool{
{Type: "function", Name: "send"},
{Type: "custom", Name: "skip_me"},
}},
{Type: "namespace", Name: "crm", Children: []ResponsesTool{
{Type: "function", Name: "query"},
}},
{Type: "function", Name: "wait"},
}
m := NamespaceToolNames(tools)
require.Len(t, m, 2)
assert.Equal(t, NamespacedToolName{Namespace: "gmail", Name: "send"}, m["gmail__send"])
assert.Equal(t, NamespacedToolName{Namespace: "crm", Name: "query"}, m["crm__query"])
// 摊平名超长时截断加哈希,无法按字符串切分还原,必须经映射反查。
longNS := "very_long_namespace_prefix_for_testing_purposes"
longChild := "and_a_rather_long_tool_name_too"
m2 := NamespaceToolNames([]ResponsesTool{{
Type: "namespace", Name: longNS,
Tools: []ResponsesTool{{Type: "function", Name: longChild}},
}})
assert.Equal(t, NamespacedToolName{Namespace: longNS, Name: longChild},
m2[flattenNamespaceToolName(longNS, longChild)])
assert.Nil(t, NamespaceToolNames(nil))
}
// codex 按 namespace+name 路由 namespace 子工具的调用:回程必须把摊平名还原为
// 裸子工具名并带独立 namespace 字段,平铺名的 function_call 会被 codex 判为
// unsupported call 拒绝执行。
func TestChatCompletionsResponseToResponses_NamespacedToolCallRestored(t *testing.T) {
resp := &ChatCompletionsResponse{
ID: "cc-1",
Choices: []ChatChoice{{
Message: ChatMessage{
Role: "assistant",
ToolCalls: []ChatToolCall{
{ID: "call_n", Function: ChatFunctionCall{Name: "mcp__svc__echo", Arguments: `{"text":"hi"}`}},
{ID: "call_9", Function: ChatFunctionCall{Name: "wait", Arguments: `{"cell_id": 3}`}},
},
},
}},
}
nsTools := map[string]NamespacedToolName{
"mcp__svc__echo": {Namespace: "mcp__svc", Name: "echo"},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, false, nsTools)
require.Len(t, out.Output, 2)
item := out.Output[0]
assert.Equal(t, "function_call", item.Type)
assert.Equal(t, "echo", item.Name)
assert.Equal(t, "mcp__svc", item.Namespace)
assert.Equal(t, "call_n", item.CallID)
assert.Equal(t, `{"text":"hi"}`, item.Arguments)
// 非流式响应体走 ResponsesOutput.MarshalJSON,namespace 必须落到线上 JSON。
b, err := json.Marshal(item)
require.NoError(t, err)
assert.Contains(t, string(b), `"namespace":"mcp__svc"`)
assert.Contains(t, string(b), `"name":"echo"`)
// 未命中映射的普通 function 调用不受影响,且不携带 namespace 字段。
assert.Equal(t, "wait", out.Output[1].Name)
assert.Empty(t, out.Output[1].Namespace)
b2, err := json.Marshal(out.Output[1])
require.NoError(t, err)
assert.NotContains(t, string(b2), `"namespace"`)
}
func TestChatCompletionsChunkToResponsesEvents_NamespacedToolCallStream(t *testing.T) {
state := NewChatCompletionsToResponsesStreamState("glm-5.2")
state.NamespaceTools = map[string]NamespacedToolName{
"mcp__svc__echo": {Namespace: "mcp__svc", Name: "echo"},
}
idx := 0
chunk := &ChatCompletionsChunk{
ID: "cc-1",
Choices: []ChatChunkChoice{{
Delta: ChatDelta{
ToolCalls: []ChatToolCall{{
Index: &idx,
ID: "call_n",
Function: ChatFunctionCall{Name: "mcp__svc__echo", Arguments: `{"text":"hi"}`},
}},
},
}},
}
events := ChatCompletionsChunkToResponsesEvents(chunk, state)
events = append(events, FinalizeChatCompletionsResponsesStream(state)...)
var added, itemDone *ResponsesStreamEvent
for i := range events {
evt := &events[i]
switch evt.Type {
case "response.output_item.added":
if evt.Item != nil && evt.Item.Type != "message" && evt.Item.Type != "reasoning" {
added = evt
}
case "response.output_item.done":
if evt.Item != nil && evt.Item.Type == "function_call" {
itemDone = evt
}
case "response.custom_tool_call_input.delta", "response.custom_tool_call_input.done":
t.Fatalf("namespace 子工具调用不应产出 custom 事件: %s", evt.Type)
}
}
require.NotNil(t, added, "缺少 namespace 调用的 output_item.added")
assert.Equal(t, "function_call", added.Item.Type)
assert.Equal(t, "echo", added.Item.Name)
assert.Equal(t, "mcp__svc", added.Item.Namespace)
require.NotNil(t, itemDone, "缺少 namespace 调用的 output_item.done")
assert.Equal(t, "call_n", itemDone.Item.CallID)
assert.Equal(t, "echo", itemDone.Item.Name)
assert.Equal(t, "mcp__svc", itemDone.Item.Namespace)
assert.Equal(t, `{"text":"hi"}`, itemDone.Item.Arguments)
// SSE 线上形态经 responsesItemWire 白名单重组,必须单独断言 namespace 落线。
sse, err := ResponsesEventToSSE(*itemDone)
require.NoError(t, err)
assert.Contains(t, sse, `"namespace":"mcp__svc"`)
assert.Contains(t, sse, `"name":"echo"`)
assert.Contains(t, sse, `"call_id":"call_n"`)
// response.completed 的 output 数组同样携带还原后的 namespace 调用项。
final := events[len(events)-1]
require.Equal(t, "response.completed", final.Type)
require.NotNil(t, final.Response)
found := false
for _, item := range final.Response.Output {
if item.Type == "function_call" {
found = true
assert.Equal(t, "echo", item.Name)
assert.Equal(t, "mcp__svc", item.Namespace)
}
}
assert.True(t, found, "response.completed 缺少还原后的 namespace 调用项")
}
func TestChatCompletionsChunkToResponsesEvents_NamespacedToolNameArrivesLate(t *testing.T) {
state := NewChatCompletionsToResponsesStreamState("glm-5.2")
state.NamespaceTools = map[string]NamespacedToolName{
"mcp__svc__echo": {Namespace: "mcp__svc", Name: "echo"},
}
idx := 0
chunk1 := &ChatCompletionsChunk{Choices: []ChatChunkChoice{{Delta: ChatDelta{
ToolCalls: []ChatToolCall{{Index: &idx, ID: "call_n", Function: ChatFunctionCall{Arguments: `{"te`}}},
}}}}
chunk2 := &ChatCompletionsChunk{Choices: []ChatChunkChoice{{Delta: ChatDelta{
ToolCalls: []ChatToolCall{{Index: &idx, Function: ChatFunctionCall{Name: "mcp__svc__echo", Arguments: `xt":"hi"}`}}},
}}}}
var events []ResponsesStreamEvent
events = append(events, ChatCompletionsChunkToResponsesEvents(chunk1, state)...)
events = append(events, ChatCompletionsChunkToResponsesEvents(chunk2, state)...)
events = append(events, FinalizeChatCompletionsResponsesStream(state)...)
addedCount := 0
deltas := ""
for _, evt := range events {
switch evt.Type {
case "response.output_item.added":
if evt.Item != nil && evt.Item.Type != "reasoning" && evt.Item.Type != "message" {
addedCount++
assert.Equal(t, "echo", evt.Item.Name, "迟到的名字命中 namespace 映射时按还原名宣告")
assert.Equal(t, "mcp__svc", evt.Item.Namespace)
}
case "response.function_call_arguments.delta":
deltas += evt.Delta
}
}
assert.Equal(t, 1, addedCount, "工具调用只宣告一次")
assert.Equal(t, `{"text":"hi"}`, deltas, "宣告前累积的参数需在宣告时补发")
}
func TestChatCompletionsChunkToResponsesEvents_FunctionToolStreamUnaffected(t *testing.T) {
state := NewChatCompletionsToResponsesStreamState("glm-5.2")
state.CustomTools = map[string]bool{"exec": true}
@@ -418,7 +418,7 @@ func TestChatCompletionsResponseToResponses_DeepSeekReasoningOnlyFallsBackToMess
}},
}
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false)
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false, nil)
require.Len(t, out.Output, 2)
require.Equal(t, "reasoning", out.Output[0].Type)
@@ -452,7 +452,7 @@ func TestChatCompletionsResponseToResponses_DeepSeekReasoningToolCallDoesNotFall
}},
}
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false)
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false, nil)
require.Len(t, out.Output, 2)
require.Equal(t, "reasoning", out.Output[0].Type)
@@ -184,6 +184,11 @@ func responsesItemWire(item *ResponsesOutput) map[string]any {
m["call_id"] = item.CallID
m["name"] = item.Name
m["arguments"] = item.Arguments
// namespace 子工具的还原调用:codex 按 namespace+name 路由,缺少该字段
// 会被判为 unsupported call。
if item.Namespace != "" {
m["namespace"] = item.Namespace
}
case "custom_tool_call":
// custom/freeform 工具调用(如 codex 的 exec):input 为自由文本。缺少
// call_id/name 时 codex 无法路由该调用(表现为 unsupported call)。
+2
View File
@@ -321,6 +321,8 @@ type ResponsesOutput struct {
CallID string `json:"call_id,omitempty"`
Name string `json:"name,omitempty"`
Arguments string `json:"arguments,omitempty"`
// 来源为 namespace 子工具时的归属命名空间(codex 按 namespace+name 路由该调用)。
Namespace string `json:"namespace,omitempty"`
// type=custom_tool_call(custom/freeform 工具,input 为自由文本)
Input string `json:"input,omitempty"`
@@ -140,7 +140,7 @@ func (s *OpenAIGatewayService) bufferChatCompletionsAsAnthropic(
if err != nil {
return nil, err
}
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, nil, false)
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, nil, false, nil)
anthropicResp := apicompat.ResponsesToAnthropic(responsesResp, originalModel)
@@ -42,9 +42,11 @@ func (s *OpenAIGatewayService) forwardResponsesViaRawChatCompletions(
serviceTier := extractOpenAIServiceTierFromBody(body)
// custom 工具(如 codex 的 exec)降级为 function 工具转发,回程需按名字还原为
// custom_tool_call 项,先记下名字集合;tool_search 工具同理,回程还原为
// tool_search_call 项。
// tool_search_call 项;namespace 子工具(如 MCP 工具)摊平转发,回程按映射还原
// 为带 namespace 字段的 function_call 项。
customTools := apicompat.CustomToolNames(responsesReq.Tools)
toolSearch := apicompat.HasToolSearchTool(responsesReq.Tools)
namespaceTools := apicompat.NamespaceToolNames(responsesReq.Tools)
chatReq, err := apicompat.ResponsesToChatCompletionsRequest(&responsesReq)
if err != nil {
@@ -105,9 +107,9 @@ func (s *OpenAIGatewayService) forwardResponsesViaRawChatCompletions(
}
if clientStream {
return s.streamChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
return s.streamChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
}
return s.bufferChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
return s.bufferChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
}
func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
@@ -116,6 +118,7 @@ func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
originalModel string,
customTools map[string]bool,
toolSearch bool,
namespaceTools map[string]apicompat.NamespacedToolName,
billingModel string,
upstreamModel string,
reasoningEffort *string,
@@ -127,7 +130,7 @@ func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
if err != nil {
return nil, err
}
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, customTools, toolSearch)
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, customTools, toolSearch, namespaceTools)
if s.responseHeaderFilter != nil {
responseheaders.WriteFilteredHeaders(c.Writer.Header(), resp.Header, s.responseHeaderFilter)
@@ -153,6 +156,7 @@ func (s *OpenAIGatewayService) streamChatCompletionsAsResponses(
originalModel string,
customTools map[string]bool,
toolSearch bool,
namespaceTools map[string]apicompat.NamespacedToolName,
billingModel string,
upstreamModel string,
reasoningEffort *string,
@@ -165,6 +169,7 @@ func (s *OpenAIGatewayService) streamChatCompletionsAsResponses(
state := apicompat.NewChatCompletionsToResponsesStreamState(originalModel)
state.CustomTools = customTools
state.ToolSearchDeclared = toolSearch
state.NamespaceTools = namespaceTools
clientDisconnected := false
writeEvents := func(events []apicompat.ResponsesStreamEvent) {