Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
6411645f
Commit
6411645f
authored
Mar 07, 2026
by
shaw
Browse files
fix: 适配claude code调度openai账号的websearch功能
parent
c0c322ba
Changes
3
Hide whitespace changes
Inline
Side-by-side
backend/internal/pkg/apicompat/anthropic_to_responses.go
View file @
6411645f
...
...
@@ -325,16 +325,22 @@ func extractAnthropicTextFromBlocks(blocks []AnthropicContentBlock) string {
}
// convertAnthropicToolsToResponses maps Anthropic tool definitions to
// Responses API function tools (input_schema → parameters).
// Responses API tools. Server-side tools like web_search are mapped to their
// OpenAI equivalents; regular tools become function tools.
func
convertAnthropicToolsToResponses
(
tools
[]
AnthropicTool
)
[]
ResponsesTool
{
out
:=
make
([]
ResponsesTool
,
len
(
tools
))
for
i
,
t
:=
range
tools
{
out
[
i
]
=
ResponsesTool
{
var
out
[]
ResponsesTool
for
_
,
t
:=
range
tools
{
// Anthropic server tools like "web_search_20250305" → OpenAI {"type":"web_search"}
if
strings
.
HasPrefix
(
t
.
Type
,
"web_search"
)
{
out
=
append
(
out
,
ResponsesTool
{
Type
:
"web_search"
})
continue
}
out
=
append
(
out
,
ResponsesTool
{
Type
:
"function"
,
Name
:
t
.
Name
,
Description
:
t
.
Description
,
Parameters
:
t
.
InputSchema
,
}
}
)
}
return
out
}
backend/internal/pkg/apicompat/responses_to_anthropic.go
View file @
6411645f
...
...
@@ -54,6 +54,25 @@ func ResponsesToAnthropic(resp *ResponsesResponse, model string) *AnthropicRespo
Name
:
item
.
Name
,
Input
:
json
.
RawMessage
(
item
.
Arguments
),
})
case
"web_search_call"
:
toolUseID
:=
"srvtoolu_"
+
item
.
ID
query
:=
""
if
item
.
Action
!=
nil
{
query
=
item
.
Action
.
Query
}
inputJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"query"
:
query
})
blocks
=
append
(
blocks
,
AnthropicContentBlock
{
Type
:
"server_tool_use"
,
ID
:
toolUseID
,
Name
:
"web_search"
,
Input
:
inputJSON
,
})
emptyResults
,
_
:=
json
.
Marshal
([]
struct
{}{})
blocks
=
append
(
blocks
,
AnthropicContentBlock
{
Type
:
"web_search_tool_result"
,
ToolUseID
:
toolUseID
,
Content
:
emptyResults
,
})
}
}
...
...
@@ -369,12 +388,73 @@ func resToAnthHandleOutputItemDone(evt *ResponsesStreamEvent, state *ResponsesEv
if
evt
.
Item
==
nil
{
return
nil
}
// Handle web_search_call → synthesize server_tool_use + web_search_tool_result blocks.
if
evt
.
Item
.
Type
==
"web_search_call"
&&
evt
.
Item
.
Status
==
"completed"
{
return
resToAnthHandleWebSearchDone
(
evt
,
state
)
}
if
state
.
ContentBlockOpen
{
return
closeCurrentBlock
(
state
)
}
return
nil
}
// resToAnthHandleWebSearchDone converts an OpenAI web_search_call output item
// into Anthropic server_tool_use + web_search_tool_result content block pairs.
// This allows Claude Code to count the searches performed.
func
resToAnthHandleWebSearchDone
(
evt
*
ResponsesStreamEvent
,
state
*
ResponsesEventToAnthropicState
)
[]
AnthropicStreamEvent
{
var
events
[]
AnthropicStreamEvent
events
=
append
(
events
,
closeCurrentBlock
(
state
)
...
)
toolUseID
:=
"srvtoolu_"
+
evt
.
Item
.
ID
query
:=
""
if
evt
.
Item
.
Action
!=
nil
{
query
=
evt
.
Item
.
Action
.
Query
}
inputJSON
,
_
:=
json
.
Marshal
(
map
[
string
]
string
{
"query"
:
query
})
// Emit server_tool_use block (start + stop).
idx1
:=
state
.
ContentBlockIndex
events
=
append
(
events
,
AnthropicStreamEvent
{
Type
:
"content_block_start"
,
Index
:
&
idx1
,
ContentBlock
:
&
AnthropicContentBlock
{
Type
:
"server_tool_use"
,
ID
:
toolUseID
,
Name
:
"web_search"
,
Input
:
inputJSON
,
},
})
events
=
append
(
events
,
AnthropicStreamEvent
{
Type
:
"content_block_stop"
,
Index
:
&
idx1
,
})
state
.
ContentBlockIndex
++
// Emit web_search_tool_result block (start + stop).
// Content is empty because OpenAI does not expose individual search results;
// the model consumes them internally and produces text output.
emptyResults
,
_
:=
json
.
Marshal
([]
struct
{}{})
idx2
:=
state
.
ContentBlockIndex
events
=
append
(
events
,
AnthropicStreamEvent
{
Type
:
"content_block_start"
,
Index
:
&
idx2
,
ContentBlock
:
&
AnthropicContentBlock
{
Type
:
"web_search_tool_result"
,
ToolUseID
:
toolUseID
,
Content
:
emptyResults
,
},
})
events
=
append
(
events
,
AnthropicStreamEvent
{
Type
:
"content_block_stop"
,
Index
:
&
idx2
,
})
state
.
ContentBlockIndex
++
return
events
}
func
resToAnthHandleCompleted
(
evt
*
ResponsesStreamEvent
,
state
*
ResponsesEventToAnthropicState
)
[]
AnthropicStreamEvent
{
if
state
.
MessageStopSent
{
return
nil
...
...
backend/internal/pkg/apicompat/types.go
View file @
6411645f
...
...
@@ -60,6 +60,7 @@ type AnthropicContentBlock struct {
// AnthropicTool describes a tool available to the model.
type
AnthropicTool
struct
{
Type
string
`json:"type,omitempty"`
// e.g. "web_search_20250305" for server tools
Name
string
`json:"name"`
Description
string
`json:"description,omitempty"`
InputSchema
json
.
RawMessage
`json:"input_schema"`
// JSON Schema object
...
...
@@ -217,7 +218,7 @@ type ResponsesIncompleteDetails struct {
// ResponsesOutput is one output item in a Responses API response.
type
ResponsesOutput
struct
{
Type
string
`json:"type"`
// "message" | "reasoning" | "function_call"
Type
string
`json:"type"`
// "message" | "reasoning" | "function_call"
| "web_search_call"
// type=message
ID
string
`json:"id,omitempty"`
...
...
@@ -233,6 +234,15 @@ type ResponsesOutput struct {
CallID
string
`json:"call_id,omitempty"`
Name
string
`json:"name,omitempty"`
Arguments
string
`json:"arguments,omitempty"`
// type=web_search_call
Action
*
WebSearchAction
`json:"action,omitempty"`
}
// WebSearchAction describes the search action in a web_search_call output item.
type
WebSearchAction
struct
{
Type
string
`json:"type,omitempty"`
// "search"
Query
string
`json:"query,omitempty"`
// primary search query
}
// ResponsesSummary is a summary text block inside a reasoning output.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment