Merge pull request #5905 from wucm667/fix/issue-5883-restore-custom-tool-alias

fix(openai): restore namespaced custom tool aliases
This commit is contained in:
Wesley Liddick
2026-08-24 11:23:37 +08:00
committed by GitHub
7 changed files with 221 additions and 24 deletions
@@ -674,7 +674,7 @@ func TestDirectBridge_NonStreamingMatchesDoubleConversion(t *testing.T) {
direct := ChatCompletionsResponseToAnthropic(resp, "claude-sonnet-4-20250514")
// Double-conversion bridge
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, false, nil)
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, nil, false, nil)
double := ResponsesToAnthropic(responsesResp, "claude-sonnet-4-20250514")
// Compare key fields
@@ -988,7 +988,7 @@ func TestDirectBridge_NonStreamingMatchesDoubleConversion_CacheWriteTokens(t *te
direct := ChatCompletionsResponseToAnthropic(resp, "claude-sonnet-4-20250514")
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, false, nil)
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, nil, false, nil)
double := ResponsesToAnthropic(responsesResp, "claude-sonnet-4-20250514")
require.Equal(t, double.Usage.InputTokens, direct.Usage.InputTokens)
@@ -1058,7 +1058,7 @@ func TestDirectBridge_NonStreamingMatchesDoubleConversion_EmptyChoices(t *testin
direct := ChatCompletionsResponseToAnthropic(resp, "claude-sonnet-4-20250514")
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, false, nil)
responsesResp := ChatCompletionsResponseToResponses(resp, "claude-sonnet-4-20250514", nil, nil, false, nil)
double := ResponsesToAnthropic(responsesResp, "claude-sonnet-4-20250514")
require.Equal(t, AnthropicStopReasonString(double.StopReason), AnthropicStopReasonString(direct.StopReason))
@@ -44,7 +44,7 @@ func TestChatReasoningAlias_AnthropicStreaming(t *testing.T) {
func TestChatReasoningAlias_ResponsesSharedPaths(t *testing.T) {
var response ChatCompletionsResponse
readIssue5302Fixture(t, "nonstream_reasoning.json", &response)
nonStream := ChatCompletionsResponseToResponses(&response, "reasoning-model", nil, false, nil)
nonStream := ChatCompletionsResponseToResponses(&response, "reasoning-model", nil, nil, false, nil)
require.Len(t, nonStream.Output, 2)
require.Equal(t, "reasoning", nonStream.Output[0].Type)
require.Equal(t, "fallback reasoning", nonStream.Output[0].Summary[0].Text)
@@ -159,6 +159,20 @@ func CustomToolNames(tools []ResponsesTool) map[string]bool {
return out
}
// FunctionToolNames collects explicitly declared top-level function tools.
func FunctionToolNames(tools []ResponsesTool) map[string]bool {
var out map[string]bool
for _, tool := range tools {
if tool.Type == "function" && tool.Name != "" {
if out == nil {
out = make(map[string]bool)
}
out[tool.Name] = true
}
}
return out
}
// NamespacedToolName 记录 namespace 子工具的原始归属(命名空间 + 裸子工具名)。
type NamespacedToolName struct {
Namespace string
@@ -197,6 +211,43 @@ func NamespaceToolNames(tools []ResponsesTool) map[string]NamespacedToolName {
return out
}
// customToolCallName restores both the exact downgraded custom-tool name and
// namespace-prefixed aliases that chat models sometimes infer from neighboring
// flattened namespace tools (for example functions__exec beside
// functions__wait). A real declared namespace child always owns its flattened
// name, and ambiguous aliases are left as ordinary function calls.
func customToolCallName(name string, customTools, functionTools map[string]bool, namespaceTools map[string]NamespacedToolName) (string, bool) {
if functionTools[name] {
return "", false
}
if customTools[name] {
return name, true
}
if _, ok := namespaceTools[name]; ok {
return "", false
}
match := ""
for customName := range customTools {
for _, namespaceTool := range namespaceTools {
if flattenNamespaceToolName(namespaceTool.Namespace, customName) != name {
continue
}
if match != "" && match != customName {
return "", false
}
match = customName
}
}
return match, match != ""
}
func customNameForStreamTool(state *ChatCompletionsToResponsesStreamState, name string) string {
if customName, ok := customToolCallName(name, state.CustomTools, state.FunctionTools, state.NamespaceTools); ok {
return customName
}
return name
}
// HasToolSearchTool 判断 Responses 请求是否声明了 tool_search 服务端工具。chat 桥
// 回程时需据此把模型对代理工具的调用还原为 tool_search_call 项:codex 只在该项类型
// 且 execution=client 时执行 tool search,同名 function_call 会因 payload 不匹配
@@ -918,6 +969,9 @@ func responsesToolsToChatTools(tools []ResponsesTool) ([]ChatTool, error) {
topLevel := make(map[string]bool)
for _, tool := range tools {
if (tool.Type == "function" || tool.Type == "custom") && tool.Name != "" {
if topLevel[tool.Name] {
return nil, fmt.Errorf("duplicate top-level executable tool name %q; this upstream cannot disambiguate duplicate names, rename one of the tools", tool.Name)
}
topLevel[tool.Name] = true
}
}
@@ -1148,7 +1202,7 @@ func extractCustomToolCallInput(arguments string) string {
// toolSearch 表示客户端声明了 tool_search 工具(见 HasToolSearchTool),代理工具
// 的调用会还原为 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 {
func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model string, customTools, functionTools map[string]bool, toolSearch bool, namespaceTools map[string]NamespacedToolName) *ResponsesResponse {
id := ""
if resp != nil {
id = resp.ID
@@ -1173,7 +1227,7 @@ func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model str
if len(resp.Choices) > 0 {
choice := resp.Choices[0]
out.Output = chatMessageToResponsesOutput(choice.Message, customTools, toolSearch, namespaceTools)
out.Output = chatMessageToResponsesOutput(choice.Message, customTools, functionTools, toolSearch, namespaceTools)
if choice.FinishReason == "length" {
out.Status = "incomplete"
out.IncompleteDetails = &ResponsesIncompleteDetails{Reason: "max_output_tokens"}
@@ -1188,7 +1242,7 @@ func ChatCompletionsResponseToResponses(resp *ChatCompletionsResponse, model str
return out
}
func chatMessageToResponsesOutput(message ChatMessage, customTools map[string]bool, toolSearch bool, namespaceTools map[string]NamespacedToolName) []ResponsesOutput {
func chatMessageToResponsesOutput(message ChatMessage, customTools, functionTools map[string]bool, toolSearch bool, namespaceTools map[string]NamespacedToolName) []ResponsesOutput {
var outputs []ResponsesOutput
reasoning := message.reasoningText()
if reasoning != "" {
@@ -1224,12 +1278,12 @@ func chatMessageToResponsesOutput(message ChatMessage, customTools map[string]bo
if strings.TrimSpace(arguments) == "" {
arguments = "{}"
}
if customTools[toolCall.Function.Name] {
if customName, ok := customToolCallName(toolCall.Function.Name, customTools, functionTools, namespaceTools); ok {
outputs = append(outputs, ResponsesOutput{
Type: "custom_tool_call",
ID: generateItemID(),
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: customName,
Input: extractCustomToolCallInput(arguments),
Status: "completed",
})
@@ -1396,6 +1450,9 @@ type ChatCompletionsToResponsesStreamState struct {
// 路由回它注册的 custom 工具。
CustomTools map[string]bool
// FunctionTools is the set of explicitly declared top-level function tools.
FunctionTools map[string]bool
// ToolSearchDeclared 表示客户端请求声明了 tool_search 工具(见
// HasToolSearchTool)。命中的代理调用按 tool_search_call 项还原,codex 只按
// 该项类型(且 execution=client)执行 tool search。
@@ -1796,11 +1853,11 @@ func announceChatToolItem(
if state.toolAnnounced[idx] {
return nil
}
if !force && stored.Function.Name == "" && (len(state.CustomTools) > 0 || state.ToolSearchDeclared || len(state.NamespaceTools) > 0) {
if !force && stored.Function.Name == "" && (len(state.CustomTools) > 0 || len(state.FunctionTools) > 0 || state.ToolSearchDeclared || len(state.NamespaceTools) > 0) {
return nil
}
state.toolAnnounced[idx] = true
isCustom := state.CustomTools[stored.Function.Name]
customName, isCustom := customToolCallName(stored.Function.Name, state.CustomTools, state.FunctionTools, state.NamespaceTools)
isToolSearch := !isCustom && state.ToolSearchDeclared && stored.Function.Name == toolSearchProxyName
state.toolIsCustom[idx] = isCustom
state.toolIsToolSearch[idx] = isToolSearch
@@ -1814,6 +1871,9 @@ func announceChatToolItem(
// namespace 子工具的调用仍按 function_call 生命周期下发,但 added/done 项要
// 还原为裸子工具名 + namespace 字段(codex 按 namespace+name 路由)。
itemName, itemNamespace := stored.Function.Name, ""
if isCustom {
itemName = customName
}
if ns, ok := state.NamespaceTools[stored.Function.Name]; ok && !isCustom && !isToolSearch {
state.toolNamespace[idx] = ns
itemName, itemNamespace = ns.Name, ns.Namespace
@@ -1883,7 +1943,7 @@ func closeChatToolItems(state *ChatCompletionsToResponsesStreamState) []Response
OutputIndex: outputIndex,
ItemID: itemID,
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: customNameForStreamTool(state, toolCall.Function.Name),
Input: input,
}),
chatToResponsesEvent(state, "response.output_item.done", &ResponsesStreamEvent{
@@ -1892,7 +1952,7 @@ func closeChatToolItems(state *ChatCompletionsToResponsesStreamState) []Response
Type: "custom_tool_call",
ID: itemID,
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: customNameForStreamTool(state, toolCall.Function.Name),
Input: input,
Status: "completed",
},
@@ -1983,7 +2043,7 @@ func (state *ChatCompletionsToResponsesStreamState) chatOutput() []ResponsesOutp
Type: "custom_tool_call",
ID: generateItemID(),
CallID: toolCall.ID,
Name: toolCall.Function.Name,
Name: customNameForStreamTool(state, toolCall.Function.Name),
Input: extractCustomToolCallInput(arguments),
Status: "completed",
})
@@ -34,6 +34,60 @@ func TestResponsesToChatCompletionsRequest_CustomToolBecomesFunctionTool(t *test
assert.Equal(t, "wait", out.Tools[1].Function.Name)
}
func TestResponsesChatBridge_MixedCustomAndNamespaceToolNames(t *testing.T) {
req := &ResponsesRequest{
Model: "deepseek-test",
Input: json.RawMessage(`"run pwd"`),
Tools: []ResponsesTool{
{Type: "custom", Name: "exec", Description: "Runs a command"},
{Type: "namespace", Name: "functions", Tools: []ResponsesTool{
{Type: "function", Name: "wait", Parameters: json.RawMessage(`{"type":"object"}`)},
}},
},
}
chatReq, err := ResponsesToChatCompletionsRequest(req)
require.NoError(t, err)
require.Len(t, chatReq.Tools, 2)
assert.Equal(t, "exec", chatReq.Tools[0].Function.Name)
assert.Equal(t, "functions__wait", chatReq.Tools[1].Function.Name)
customTools := CustomToolNames(req.Tools)
namespaceTools := NamespaceToolNames(req.Tools)
resp := &ChatCompletionsResponse{Choices: []ChatChoice{{Message: ChatMessage{ToolCalls: []ChatToolCall{
{ID: "call_exact", Function: ChatFunctionCall{Name: "exec", Arguments: `{"input":"pwd"}`}},
{ID: "call_wait", Function: ChatFunctionCall{Name: "functions__wait", Arguments: `{"cell_id":"1"}`}},
{ID: "call_alias", Function: ChatFunctionCall{Name: "functions__exec", Arguments: `not-json`}},
}}}}}
out := ChatCompletionsResponseToResponses(resp, req.Model, customTools, FunctionToolNames(req.Tools), false, namespaceTools)
require.Len(t, out.Output, 3)
assert.Equal(t, "custom_tool_call", out.Output[0].Type)
assert.Equal(t, "exec", out.Output[0].Name)
assert.Equal(t, "pwd", out.Output[0].Input)
assert.Equal(t, "function_call", out.Output[1].Type)
assert.Equal(t, "functions", out.Output[1].Namespace)
assert.Equal(t, "wait", out.Output[1].Name)
assert.Equal(t, "custom_tool_call", out.Output[2].Type)
assert.Equal(t, "exec", out.Output[2].Name)
assert.Equal(t, "not-json", out.Output[2].Input)
}
func TestChatCompletionsResponseToResponses_ExplicitFunctionOwnsCustomAliasCollision(t *testing.T) {
resp := &ChatCompletionsResponse{Choices: []ChatChoice{{Message: ChatMessage{ToolCalls: []ChatToolCall{{
ID: "call_function", Function: ChatFunctionCall{Name: "functions__exec", Arguments: `{"path":"/tmp"}`},
}}}}}}
out := ChatCompletionsResponseToResponses(resp, "deepseek-test",
map[string]bool{"exec": true}, map[string]bool{"functions__exec": true}, false,
map[string]NamespacedToolName{"functions__wait": {Namespace: "functions", Name: "wait"}})
require.Len(t, out.Output, 1)
assert.Equal(t, "function_call", out.Output[0].Type)
assert.Equal(t, "functions__exec", out.Output[0].Name)
assert.Equal(t, `{"path":"/tmp"}`, out.Output[0].Arguments)
}
func TestResponsesToChatCompletionsRequest_AdditionalToolsItem(t *testing.T) {
req := &ResponsesRequest{
Model: "gpt-test",
@@ -173,7 +227,7 @@ func TestChatCompletionsResponseToResponses_CustomToolCallOutputItem(t *testing.
}},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", map[string]bool{"exec": true}, false, nil)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", map[string]bool{"exec": true}, nil, false, nil)
require.Len(t, out.Output, 2)
assert.Equal(t, "custom_tool_call", out.Output[0].Type)
@@ -264,6 +318,68 @@ func TestChatCompletionsChunkToResponsesEvents_CustomToolCallStream(t *testing.T
assert.True(t, foundCustom, "response.completed 缺少 custom_tool_call 输出项")
}
func TestChatCompletionsChunkToResponsesEvents_MixedCustomNamespaceAliasStream(t *testing.T) {
state := NewChatCompletionsToResponsesStreamState("deepseek-test")
state.CustomTools = map[string]bool{"exec": true}
state.NamespaceTools = map[string]NamespacedToolName{
"functions__wait": {Namespace: "functions", Name: "wait"},
}
idx := 0
chunk := &ChatCompletionsChunk{Choices: []ChatChunkChoice{{Delta: ChatDelta{ToolCalls: []ChatToolCall{{
Index: &idx, ID: "call_alias", Function: ChatFunctionCall{Name: "functions__exec", Arguments: `not-json`},
}}}}}}
events := ChatCompletionsChunkToResponsesEvents(chunk, state)
events = append(events, FinalizeChatCompletionsResponsesStream(state)...)
for _, evt := range events {
if evt.Type == "response.output_item.added" && evt.Item != nil && evt.Item.Type == "custom_tool_call" {
assert.Equal(t, "exec", evt.Item.Name)
}
if evt.Type == "response.custom_tool_call_input.done" {
assert.Equal(t, "exec", evt.Name)
assert.Equal(t, "not-json", evt.Input)
}
if evt.Type == "response.output_item.done" && evt.Item != nil && evt.Item.Type == "custom_tool_call" {
assert.Equal(t, "exec", evt.Item.Name)
assert.Equal(t, "not-json", evt.Item.Input)
}
}
final := events[len(events)-1]
require.Equal(t, "response.completed", final.Type)
require.Len(t, final.Response.Output, 1)
assert.Equal(t, "custom_tool_call", final.Response.Output[0].Type)
assert.Equal(t, "exec", final.Response.Output[0].Name)
assert.Equal(t, "not-json", final.Response.Output[0].Input)
}
func TestChatCompletionsChunkToResponsesEvents_ExplicitFunctionOwnsCustomAliasCollision(t *testing.T) {
state := NewChatCompletionsToResponsesStreamState("deepseek-test")
state.CustomTools = map[string]bool{"exec": true}
state.FunctionTools = map[string]bool{"functions__exec": true}
state.NamespaceTools = map[string]NamespacedToolName{
"functions__wait": {Namespace: "functions", Name: "wait"},
}
idx := 0
chunk := &ChatCompletionsChunk{Choices: []ChatChunkChoice{{Delta: ChatDelta{ToolCalls: []ChatToolCall{{
Index: &idx, ID: "call_function", Function: ChatFunctionCall{Name: "functions__exec", Arguments: `{"path":"/tmp"}`},
}}}}}}
events := ChatCompletionsChunkToResponsesEvents(chunk, state)
events = append(events, FinalizeChatCompletionsResponsesStream(state)...)
for _, evt := range events {
assert.NotEqual(t, "response.custom_tool_call_input.done", evt.Type)
}
final := events[len(events)-1]
require.Equal(t, "response.completed", final.Type)
require.Len(t, final.Response.Output, 1)
assert.Equal(t, "function_call", final.Response.Output[0].Type)
assert.Equal(t, "functions__exec", final.Response.Output[0].Name)
assert.Equal(t, `{"path":"/tmp"}`, final.Response.Output[0].Arguments)
}
func TestResponsesToChatCompletionsRequest_ToolSearchToolBecomesProxyFunction(t *testing.T) {
req := &ResponsesRequest{
Model: "glm-5.2",
@@ -308,7 +424,7 @@ func TestChatCompletionsResponseToResponses_ToolSearchCallOutputItem(t *testing.
}},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, true, nil)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, nil, true, nil)
require.Len(t, out.Output, 1)
item := out.Output[0]
@@ -340,7 +456,7 @@ func TestChatCompletionsResponseToResponses_ToolSearchNotDeclaredKeepsFunctionCa
}
// 客户端未声明 type=tool_search 时,同名普通 function 工具不受影响。
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, false, nil)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, nil, false, nil)
require.Len(t, out.Output, 1)
assert.Equal(t, "function_call", out.Output[0].Type)
}
@@ -656,6 +772,23 @@ func TestResponsesToChatCompletionsRequest_RejectsToolSearchNameConflict(t *test
assert.Equal(t, "tool_search", out.Tools[0].Function.Name)
}
func TestResponsesToChatCompletionsRequest_RejectsDuplicateTopLevelExecutableNames(t *testing.T) {
for _, tools := range [][]ResponsesTool{
{{Type: "custom", Name: "exec"}, {Type: "function", Name: "exec"}},
{{Type: "function", Name: "exec"}, {Type: "function", Name: "exec"}},
{{Type: "custom", Name: "exec"}, {Type: "custom", Name: "exec"}},
} {
_, err := ResponsesToChatCompletionsRequest(&ResponsesRequest{
Model: "glm-5.2",
Input: json.RawMessage(`"hi"`),
Tools: tools,
})
require.Error(t, err)
assert.Contains(t, err.Error(), "exec")
assert.Contains(t, err.Error(), "cannot disambiguate")
}
}
// tool_choice 指向被转换丢弃的工具(如 web_search)或不存在的名字时不能原样转发,
// chat 上游会因选择项指向未声明工具而 400;字符串形式与指向幸存工具的选择保持转发。
func TestResponsesToChatCompletionsRequest_DropsToolChoiceForDroppedTool(t *testing.T) {
@@ -807,7 +940,7 @@ func TestChatCompletionsResponseToResponses_NamespacedToolCallRestored(t *testin
"mcp__svc__echo": {Namespace: "mcp__svc", Name: "echo"},
}
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, false, nsTools)
out := ChatCompletionsResponseToResponses(resp, "glm-5.2", nil, nil, false, nsTools)
require.Len(t, out.Output, 2)
item := out.Output[0]
@@ -65,7 +65,7 @@ func TestChatCompletionsResponseToResponses_SkipsInvalidFunctionArguments(t *tes
}},
}
out := ChatCompletionsResponseToResponses(resp, "deepseek-v4-flash", nil, false, nil)
out := ChatCompletionsResponseToResponses(resp, "deepseek-v4-flash", nil, nil, false, nil)
require.Equal(t, "incomplete", out.Status)
require.Len(t, out.Output, 1)
require.Equal(t, "function_call", out.Output[0].Type)
@@ -532,7 +532,7 @@ func TestChatCompletionsResponseToResponses_DeepSeekReasoningOnlyFallsBackToMess
}},
}
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false, nil)
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, nil, false, nil)
require.Len(t, out.Output, 2)
require.Equal(t, "reasoning", out.Output[0].Type)
@@ -566,7 +566,7 @@ func TestChatCompletionsResponseToResponses_DeepSeekReasoningToolCallDoesNotFall
}},
}
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, false, nil)
out := ChatCompletionsResponseToResponses(resp, "deepseek-reasoner", nil, nil, false, nil)
require.Len(t, out.Output, 2)
require.Equal(t, "reasoning", out.Output[0].Type)
@@ -50,6 +50,7 @@ func (s *OpenAIGatewayService) forwardResponsesViaRawChatCompletions(
return nil, fmt.Errorf("resolve responses tools: %w", err)
}
customTools := apicompat.CustomToolNames(effectiveTools)
functionTools := apicompat.FunctionToolNames(effectiveTools)
toolSearch := apicompat.HasToolSearchTool(effectiveTools)
namespaceTools := apicompat.NamespaceToolNames(effectiveTools)
@@ -120,9 +121,9 @@ func (s *OpenAIGatewayService) forwardResponsesViaRawChatCompletions(
}
if clientStream {
return s.streamChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
return s.streamChatCompletionsAsResponses(c, resp, originalModel, customTools, functionTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
}
return s.bufferChatCompletionsAsResponses(c, resp, originalModel, customTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
return s.bufferChatCompletionsAsResponses(c, resp, originalModel, customTools, functionTools, toolSearch, namespaceTools, billingModel, upstreamModel, reasoningEffort, serviceTier, startTime)
}
func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
@@ -130,6 +131,7 @@ func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
resp *http.Response,
originalModel string,
customTools map[string]bool,
functionTools map[string]bool,
toolSearch bool,
namespaceTools map[string]apicompat.NamespacedToolName,
billingModel string,
@@ -143,7 +145,7 @@ func (s *OpenAIGatewayService) bufferChatCompletionsAsResponses(
if err != nil {
return nil, err
}
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, customTools, toolSearch, namespaceTools)
responsesResp := apicompat.ChatCompletionsResponseToResponses(ccResp, originalModel, customTools, functionTools, toolSearch, namespaceTools)
s.cacheReasoningItemsFromOutput(responsesResp.Output)
if s.responseHeaderFilter != nil {
@@ -169,6 +171,7 @@ func (s *OpenAIGatewayService) streamChatCompletionsAsResponses(
resp *http.Response,
originalModel string,
customTools map[string]bool,
functionTools map[string]bool,
toolSearch bool,
namespaceTools map[string]apicompat.NamespacedToolName,
billingModel string,
@@ -182,6 +185,7 @@ func (s *OpenAIGatewayService) streamChatCompletionsAsResponses(
state := apicompat.NewChatCompletionsToResponsesStreamState(originalModel)
state.CustomTools = customTools
state.FunctionTools = functionTools
state.ToolSearchDeclared = toolSearch
state.NamespaceTools = namespaceTools
clientDisconnected := false