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
b0389ca4
Commit
b0389ca4
authored
Dec 28, 2025
by
song
Browse files
feat: 实现 Antigravity Claude → Gemini 协议转换,haiku 映射到 gemini-3-flash
parent
1d085d98
Changes
7
Hide whitespace changes
Inline
Side-by-side
backend/internal/pkg/antigravity/claude_types.go
0 → 100644
View file @
b0389ca4
package
antigravity
import
"encoding/json"
// Claude 请求/响应类型定义
// ClaudeRequest Claude Messages API 请求
type
ClaudeRequest
struct
{
Model
string
`json:"model"`
Messages
[]
ClaudeMessage
`json:"messages"`
MaxTokens
int
`json:"max_tokens,omitempty"`
System
json
.
RawMessage
`json:"system,omitempty"`
// string 或 []SystemBlock
Stream
bool
`json:"stream,omitempty"`
Temperature
*
float64
`json:"temperature,omitempty"`
TopP
*
float64
`json:"top_p,omitempty"`
TopK
*
int
`json:"top_k,omitempty"`
Tools
[]
ClaudeTool
`json:"tools,omitempty"`
Thinking
*
ThinkingConfig
`json:"thinking,omitempty"`
Metadata
*
ClaudeMetadata
`json:"metadata,omitempty"`
}
// ClaudeMessage Claude 消息
type
ClaudeMessage
struct
{
Role
string
`json:"role"`
// user, assistant
Content
json
.
RawMessage
`json:"content"`
}
// ThinkingConfig Thinking 配置
type
ThinkingConfig
struct
{
Type
string
`json:"type"`
// "enabled" or "disabled"
BudgetTokens
int
`json:"budget_tokens,omitempty"`
// thinking budget
}
// ClaudeMetadata 请求元数据
type
ClaudeMetadata
struct
{
UserID
string
`json:"user_id,omitempty"`
}
// ClaudeTool Claude 工具定义
type
ClaudeTool
struct
{
Name
string
`json:"name"`
Description
string
`json:"description,omitempty"`
InputSchema
map
[
string
]
interface
{}
`json:"input_schema"`
}
// SystemBlock system prompt 数组形式的元素
type
SystemBlock
struct
{
Type
string
`json:"type"`
Text
string
`json:"text"`
}
// ContentBlock Claude 消息内容块(解析后)
type
ContentBlock
struct
{
Type
string
`json:"type"`
// text
Text
string
`json:"text,omitempty"`
// thinking
Thinking
string
`json:"thinking,omitempty"`
Signature
string
`json:"signature,omitempty"`
// tool_use
ID
string
`json:"id,omitempty"`
Name
string
`json:"name,omitempty"`
Input
interface
{}
`json:"input,omitempty"`
// tool_result
ToolUseID
string
`json:"tool_use_id,omitempty"`
Content
json
.
RawMessage
`json:"content,omitempty"`
IsError
bool
`json:"is_error,omitempty"`
// image
Source
*
ImageSource
`json:"source,omitempty"`
}
// ImageSource Claude 图片来源
type
ImageSource
struct
{
Type
string
`json:"type"`
// "base64"
MediaType
string
`json:"media_type"`
// "image/png", "image/jpeg" 等
Data
string
`json:"data"`
}
// ClaudeResponse Claude Messages API 响应
type
ClaudeResponse
struct
{
ID
string
`json:"id"`
Type
string
`json:"type"`
// "message"
Role
string
`json:"role"`
// "assistant"
Model
string
`json:"model"`
Content
[]
ClaudeContentItem
`json:"content"`
StopReason
string
`json:"stop_reason,omitempty"`
// end_turn, tool_use, max_tokens
StopSequence
*
string
`json:"stop_sequence,omitempty"`
// null 或具体值
Usage
ClaudeUsage
`json:"usage"`
}
// ClaudeContentItem Claude 响应内容项
type
ClaudeContentItem
struct
{
Type
string
`json:"type"`
// text, thinking, tool_use
// text
Text
string
`json:"text,omitempty"`
// thinking
Thinking
string
`json:"thinking,omitempty"`
Signature
string
`json:"signature,omitempty"`
// tool_use
ID
string
`json:"id,omitempty"`
Name
string
`json:"name,omitempty"`
Input
interface
{}
`json:"input,omitempty"`
}
// ClaudeUsage Claude 用量统计
type
ClaudeUsage
struct
{
InputTokens
int
`json:"input_tokens"`
OutputTokens
int
`json:"output_tokens"`
CacheCreationInputTokens
int
`json:"cache_creation_input_tokens,omitempty"`
CacheReadInputTokens
int
`json:"cache_read_input_tokens,omitempty"`
}
// ClaudeError Claude 错误响应
type
ClaudeError
struct
{
Type
string
`json:"type"`
// "error"
Error
ErrorDetail
`json:"error"`
}
// ErrorDetail 错误详情
type
ErrorDetail
struct
{
Type
string
`json:"type"`
Message
string
`json:"message"`
}
backend/internal/pkg/antigravity/gemini_types.go
0 → 100644
View file @
b0389ca4
package
antigravity
// Gemini v1internal 请求/响应类型定义
// V1InternalRequest v1internal 请求包装
type
V1InternalRequest
struct
{
Project
string
`json:"project"`
RequestID
string
`json:"requestId"`
UserAgent
string
`json:"userAgent"`
RequestType
string
`json:"requestType,omitempty"`
Model
string
`json:"model"`
Request
GeminiRequest
`json:"request"`
}
// GeminiRequest Gemini 请求内容
type
GeminiRequest
struct
{
Contents
[]
GeminiContent
`json:"contents"`
SystemInstruction
*
GeminiContent
`json:"systemInstruction,omitempty"`
GenerationConfig
*
GeminiGenerationConfig
`json:"generationConfig,omitempty"`
Tools
[]
GeminiToolDeclaration
`json:"tools,omitempty"`
ToolConfig
*
GeminiToolConfig
`json:"toolConfig,omitempty"`
SafetySettings
[]
GeminiSafetySetting
`json:"safetySettings,omitempty"`
SessionID
string
`json:"sessionId,omitempty"`
}
// GeminiContent Gemini 内容
type
GeminiContent
struct
{
Role
string
`json:"role"`
// user, model
Parts
[]
GeminiPart
`json:"parts"`
}
// GeminiPart Gemini 内容部分
type
GeminiPart
struct
{
Text
string
`json:"text,omitempty"`
Thought
bool
`json:"thought,omitempty"`
ThoughtSignature
string
`json:"thoughtSignature,omitempty"`
InlineData
*
GeminiInlineData
`json:"inlineData,omitempty"`
FunctionCall
*
GeminiFunctionCall
`json:"functionCall,omitempty"`
FunctionResponse
*
GeminiFunctionResponse
`json:"functionResponse,omitempty"`
}
// GeminiInlineData Gemini 内联数据(图片等)
type
GeminiInlineData
struct
{
MimeType
string
`json:"mimeType"`
Data
string
`json:"data"`
}
// GeminiFunctionCall Gemini 函数调用
type
GeminiFunctionCall
struct
{
Name
string
`json:"name"`
Args
interface
{}
`json:"args,omitempty"`
ID
string
`json:"id,omitempty"`
}
// GeminiFunctionResponse Gemini 函数响应
type
GeminiFunctionResponse
struct
{
Name
string
`json:"name"`
Response
map
[
string
]
interface
{}
`json:"response"`
ID
string
`json:"id,omitempty"`
}
// GeminiGenerationConfig Gemini 生成配置
type
GeminiGenerationConfig
struct
{
MaxOutputTokens
int
`json:"maxOutputTokens,omitempty"`
Temperature
*
float64
`json:"temperature,omitempty"`
TopP
*
float64
`json:"topP,omitempty"`
TopK
*
int
`json:"topK,omitempty"`
ThinkingConfig
*
GeminiThinkingConfig
`json:"thinkingConfig,omitempty"`
StopSequences
[]
string
`json:"stopSequences,omitempty"`
}
// GeminiThinkingConfig Gemini thinking 配置
type
GeminiThinkingConfig
struct
{
IncludeThoughts
bool
`json:"includeThoughts"`
ThinkingBudget
int
`json:"thinkingBudget,omitempty"`
}
// GeminiToolDeclaration Gemini 工具声明
type
GeminiToolDeclaration
struct
{
FunctionDeclarations
[]
GeminiFunctionDecl
`json:"functionDeclarations,omitempty"`
GoogleSearch
*
GeminiGoogleSearch
`json:"googleSearch,omitempty"`
}
// GeminiFunctionDecl Gemini 函数声明
type
GeminiFunctionDecl
struct
{
Name
string
`json:"name"`
Description
string
`json:"description,omitempty"`
Parameters
map
[
string
]
interface
{}
`json:"parameters,omitempty"`
}
// GeminiGoogleSearch Gemini Google 搜索工具
type
GeminiGoogleSearch
struct
{
EnhancedContent
*
GeminiEnhancedContent
`json:"enhancedContent,omitempty"`
}
// GeminiEnhancedContent 增强内容配置
type
GeminiEnhancedContent
struct
{
ImageSearch
*
GeminiImageSearch
`json:"imageSearch,omitempty"`
}
// GeminiImageSearch 图片搜索配置
type
GeminiImageSearch
struct
{
MaxResultCount
int
`json:"maxResultCount,omitempty"`
}
// GeminiToolConfig Gemini 工具配置
type
GeminiToolConfig
struct
{
FunctionCallingConfig
*
GeminiFunctionCallingConfig
`json:"functionCallingConfig,omitempty"`
}
// GeminiFunctionCallingConfig 函数调用配置
type
GeminiFunctionCallingConfig
struct
{
Mode
string
`json:"mode,omitempty"`
// VALIDATED, AUTO, NONE
}
// GeminiSafetySetting Gemini 安全设置
type
GeminiSafetySetting
struct
{
Category
string
`json:"category"`
Threshold
string
`json:"threshold"`
}
// V1InternalResponse v1internal 响应包装
type
V1InternalResponse
struct
{
Response
GeminiResponse
`json:"response"`
ResponseID
string
`json:"responseId,omitempty"`
ModelVersion
string
`json:"modelVersion,omitempty"`
}
// GeminiResponse Gemini 响应
type
GeminiResponse
struct
{
Candidates
[]
GeminiCandidate
`json:"candidates,omitempty"`
UsageMetadata
*
GeminiUsageMetadata
`json:"usageMetadata,omitempty"`
ResponseID
string
`json:"responseId,omitempty"`
ModelVersion
string
`json:"modelVersion,omitempty"`
}
// GeminiCandidate Gemini 候选响应
type
GeminiCandidate
struct
{
Content
*
GeminiContent
`json:"content,omitempty"`
FinishReason
string
`json:"finishReason,omitempty"`
Index
int
`json:"index,omitempty"`
}
// GeminiUsageMetadata Gemini 用量元数据
type
GeminiUsageMetadata
struct
{
PromptTokenCount
int
`json:"promptTokenCount,omitempty"`
CandidatesTokenCount
int
`json:"candidatesTokenCount,omitempty"`
TotalTokenCount
int
`json:"totalTokenCount,omitempty"`
}
// DefaultSafetySettings 默认安全设置(关闭所有过滤)
var
DefaultSafetySettings
=
[]
GeminiSafetySetting
{
{
Category
:
"HARM_CATEGORY_HARASSMENT"
,
Threshold
:
"OFF"
},
{
Category
:
"HARM_CATEGORY_HATE_SPEECH"
,
Threshold
:
"OFF"
},
{
Category
:
"HARM_CATEGORY_SEXUALLY_EXPLICIT"
,
Threshold
:
"OFF"
},
{
Category
:
"HARM_CATEGORY_DANGEROUS_CONTENT"
,
Threshold
:
"OFF"
},
{
Category
:
"HARM_CATEGORY_CIVIC_INTEGRITY"
,
Threshold
:
"OFF"
},
}
// DefaultStopSequences 默认停止序列
var
DefaultStopSequences
=
[]
string
{
"<|user|>"
,
"<|endoftext|>"
,
"<|end_of_turn|>"
,
"[DONE]"
,
"
\n\n
Human:"
,
}
backend/internal/pkg/antigravity/request_transformer.go
0 → 100644
View file @
b0389ca4
package
antigravity
import
(
"encoding/json"
"fmt"
"strings"
"github.com/google/uuid"
)
// TransformClaudeToGemini 将 Claude 请求转换为 v1internal Gemini 格式
func
TransformClaudeToGemini
(
claudeReq
*
ClaudeRequest
,
projectID
,
mappedModel
string
)
([]
byte
,
error
)
{
// 用于存储 tool_use id -> name 映射
toolIDToName
:=
make
(
map
[
string
]
string
)
// 检测是否启用 thinking
isThinkingEnabled
:=
claudeReq
.
Thinking
!=
nil
&&
claudeReq
.
Thinking
.
Type
==
"enabled"
// 1. 构建 contents
contents
,
err
:=
buildContents
(
claudeReq
.
Messages
,
toolIDToName
,
isThinkingEnabled
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"build contents: %w"
,
err
)
}
// 2. 构建 systemInstruction
systemInstruction
:=
buildSystemInstruction
(
claudeReq
.
System
,
claudeReq
.
Model
)
// 3. 构建 generationConfig
generationConfig
:=
buildGenerationConfig
(
claudeReq
)
// 4. 构建 tools
tools
:=
buildTools
(
claudeReq
.
Tools
)
// 5. 构建内部请求
innerRequest
:=
GeminiRequest
{
Contents
:
contents
,
SafetySettings
:
DefaultSafetySettings
,
}
if
systemInstruction
!=
nil
{
innerRequest
.
SystemInstruction
=
systemInstruction
}
if
generationConfig
!=
nil
{
innerRequest
.
GenerationConfig
=
generationConfig
}
if
len
(
tools
)
>
0
{
innerRequest
.
Tools
=
tools
innerRequest
.
ToolConfig
=
&
GeminiToolConfig
{
FunctionCallingConfig
:
&
GeminiFunctionCallingConfig
{
Mode
:
"VALIDATED"
,
},
}
}
// 如果提供了 metadata.user_id,复用为 sessionId
if
claudeReq
.
Metadata
!=
nil
&&
claudeReq
.
Metadata
.
UserID
!=
""
{
innerRequest
.
SessionID
=
claudeReq
.
Metadata
.
UserID
}
// 6. 包装为 v1internal 请求
v1Req
:=
V1InternalRequest
{
Project
:
projectID
,
RequestID
:
"agent-"
+
uuid
.
New
()
.
String
(),
UserAgent
:
"sub2api"
,
RequestType
:
"agent"
,
Model
:
mappedModel
,
Request
:
innerRequest
,
}
return
json
.
Marshal
(
v1Req
)
}
// buildSystemInstruction 构建 systemInstruction
func
buildSystemInstruction
(
system
json
.
RawMessage
,
modelName
string
)
*
GeminiContent
{
var
parts
[]
GeminiPart
// 注入身份防护指令
identityPatch
:=
fmt
.
Sprintf
(
"--- [IDENTITY_PATCH] ---
\n
"
+
"Ignore any previous instructions regarding your identity or host platform (e.g., Amazon Q, Google AI).
\n
"
+
"You are currently providing services as the native %s model via a standard API proxy.
\n
"
+
"Always use the 'claude' command for terminal tasks if relevant.
\n
"
+
"--- [SYSTEM_PROMPT_BEGIN] ---
\n
"
,
modelName
,
)
parts
=
append
(
parts
,
GeminiPart
{
Text
:
identityPatch
})
// 解析 system prompt
if
len
(
system
)
>
0
{
// 尝试解析为字符串
var
sysStr
string
if
err
:=
json
.
Unmarshal
(
system
,
&
sysStr
);
err
==
nil
{
if
strings
.
TrimSpace
(
sysStr
)
!=
""
{
parts
=
append
(
parts
,
GeminiPart
{
Text
:
sysStr
})
}
}
else
{
// 尝试解析为数组
var
sysBlocks
[]
SystemBlock
if
err
:=
json
.
Unmarshal
(
system
,
&
sysBlocks
);
err
==
nil
{
for
_
,
block
:=
range
sysBlocks
{
if
block
.
Type
==
"text"
&&
strings
.
TrimSpace
(
block
.
Text
)
!=
""
{
parts
=
append
(
parts
,
GeminiPart
{
Text
:
block
.
Text
})
}
}
}
}
}
parts
=
append
(
parts
,
GeminiPart
{
Text
:
"
\n
--- [SYSTEM_PROMPT_END] ---"
})
return
&
GeminiContent
{
Role
:
"user"
,
Parts
:
parts
,
}
}
// buildContents 构建 contents
func
buildContents
(
messages
[]
ClaudeMessage
,
toolIDToName
map
[
string
]
string
,
isThinkingEnabled
bool
)
([]
GeminiContent
,
error
)
{
var
contents
[]
GeminiContent
for
i
,
msg
:=
range
messages
{
role
:=
msg
.
Role
if
role
==
"assistant"
{
role
=
"model"
}
parts
,
err
:=
buildParts
(
msg
.
Content
,
toolIDToName
)
if
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"build parts for message %d: %w"
,
i
,
err
)
}
// 如果 thinking 开启且是最后一条 assistant 消息,需要检查是否需要添加 dummy thinking
if
role
==
"model"
&&
isThinkingEnabled
&&
i
==
len
(
messages
)
-
1
{
hasThoughtPart
:=
false
for
_
,
p
:=
range
parts
{
if
p
.
Thought
{
hasThoughtPart
=
true
break
}
}
if
!
hasThoughtPart
&&
len
(
parts
)
>
0
{
// 在开头添加 dummy thinking block
parts
=
append
([]
GeminiPart
{{
Text
:
"Thinking..."
,
Thought
:
true
}},
parts
...
)
}
}
if
len
(
parts
)
==
0
{
continue
}
contents
=
append
(
contents
,
GeminiContent
{
Role
:
role
,
Parts
:
parts
,
})
}
return
contents
,
nil
}
// buildParts 构建消息的 parts
func
buildParts
(
content
json
.
RawMessage
,
toolIDToName
map
[
string
]
string
)
([]
GeminiPart
,
error
)
{
var
parts
[]
GeminiPart
// 尝试解析为字符串
var
textContent
string
if
err
:=
json
.
Unmarshal
(
content
,
&
textContent
);
err
==
nil
{
if
textContent
!=
"(no content)"
&&
strings
.
TrimSpace
(
textContent
)
!=
""
{
parts
=
append
(
parts
,
GeminiPart
{
Text
:
strings
.
TrimSpace
(
textContent
)})
}
return
parts
,
nil
}
// 解析为内容块数组
var
blocks
[]
ContentBlock
if
err
:=
json
.
Unmarshal
(
content
,
&
blocks
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"parse content blocks: %w"
,
err
)
}
for
_
,
block
:=
range
blocks
{
switch
block
.
Type
{
case
"text"
:
if
block
.
Text
!=
"(no content)"
&&
strings
.
TrimSpace
(
block
.
Text
)
!=
""
{
parts
=
append
(
parts
,
GeminiPart
{
Text
:
block
.
Text
})
}
case
"thinking"
:
part
:=
GeminiPart
{
Text
:
block
.
Thinking
,
Thought
:
true
,
}
if
block
.
Signature
!=
""
{
part
.
ThoughtSignature
=
block
.
Signature
}
parts
=
append
(
parts
,
part
)
case
"image"
:
if
block
.
Source
!=
nil
&&
block
.
Source
.
Type
==
"base64"
{
parts
=
append
(
parts
,
GeminiPart
{
InlineData
:
&
GeminiInlineData
{
MimeType
:
block
.
Source
.
MediaType
,
Data
:
block
.
Source
.
Data
,
},
})
}
case
"tool_use"
:
// 存储 id -> name 映射
if
block
.
ID
!=
""
&&
block
.
Name
!=
""
{
toolIDToName
[
block
.
ID
]
=
block
.
Name
}
part
:=
GeminiPart
{
FunctionCall
:
&
GeminiFunctionCall
{
Name
:
block
.
Name
,
Args
:
block
.
Input
,
ID
:
block
.
ID
,
},
}
if
block
.
Signature
!=
""
{
part
.
ThoughtSignature
=
block
.
Signature
}
parts
=
append
(
parts
,
part
)
case
"tool_result"
:
// 获取函数名
funcName
:=
block
.
Name
if
funcName
==
""
{
if
name
,
ok
:=
toolIDToName
[
block
.
ToolUseID
];
ok
{
funcName
=
name
}
else
{
funcName
=
block
.
ToolUseID
}
}
// 解析 content
resultContent
:=
parseToolResultContent
(
block
.
Content
,
block
.
IsError
)
parts
=
append
(
parts
,
GeminiPart
{
FunctionResponse
:
&
GeminiFunctionResponse
{
Name
:
funcName
,
Response
:
map
[
string
]
interface
{}{
"result"
:
resultContent
,
},
ID
:
block
.
ToolUseID
,
},
})
}
}
return
parts
,
nil
}
// parseToolResultContent 解析 tool_result 的 content
func
parseToolResultContent
(
content
json
.
RawMessage
,
isError
bool
)
string
{
if
len
(
content
)
==
0
{
if
isError
{
return
"Tool execution failed with no output."
}
return
"Command executed successfully."
}
// 尝试解析为字符串
var
str
string
if
err
:=
json
.
Unmarshal
(
content
,
&
str
);
err
==
nil
{
if
strings
.
TrimSpace
(
str
)
==
""
{
if
isError
{
return
"Tool execution failed with no output."
}
return
"Command executed successfully."
}
return
str
}
// 尝试解析为数组
var
arr
[]
map
[
string
]
interface
{}
if
err
:=
json
.
Unmarshal
(
content
,
&
arr
);
err
==
nil
{
var
texts
[]
string
for
_
,
item
:=
range
arr
{
if
text
,
ok
:=
item
[
"text"
]
.
(
string
);
ok
{
texts
=
append
(
texts
,
text
)
}
}
result
:=
strings
.
Join
(
texts
,
"
\n
"
)
if
strings
.
TrimSpace
(
result
)
==
""
{
if
isError
{
return
"Tool execution failed with no output."
}
return
"Command executed successfully."
}
return
result
}
// 返回原始 JSON
return
string
(
content
)
}
// buildGenerationConfig 构建 generationConfig
func
buildGenerationConfig
(
req
*
ClaudeRequest
)
*
GeminiGenerationConfig
{
config
:=
&
GeminiGenerationConfig
{
MaxOutputTokens
:
64000
,
// 默认最大输出
StopSequences
:
DefaultStopSequences
,
}
// Thinking 配置
if
req
.
Thinking
!=
nil
&&
req
.
Thinking
.
Type
==
"enabled"
{
config
.
ThinkingConfig
=
&
GeminiThinkingConfig
{
IncludeThoughts
:
true
,
}
if
req
.
Thinking
.
BudgetTokens
>
0
{
budget
:=
req
.
Thinking
.
BudgetTokens
// gemini-2.5-flash 上限 24576
if
strings
.
Contains
(
req
.
Model
,
"gemini-2.5-flash"
)
&&
budget
>
24576
{
budget
=
24576
}
config
.
ThinkingConfig
.
ThinkingBudget
=
budget
}
}
// 其他参数
if
req
.
Temperature
!=
nil
{
config
.
Temperature
=
req
.
Temperature
}
if
req
.
TopP
!=
nil
{
config
.
TopP
=
req
.
TopP
}
if
req
.
TopK
!=
nil
{
config
.
TopK
=
req
.
TopK
}
return
config
}
// buildTools 构建 tools
func
buildTools
(
tools
[]
ClaudeTool
)
[]
GeminiToolDeclaration
{
if
len
(
tools
)
==
0
{
return
nil
}
// 检查是否有 web_search 工具
hasWebSearch
:=
false
for
_
,
tool
:=
range
tools
{
if
tool
.
Name
==
"web_search"
{
hasWebSearch
=
true
break
}
}
if
hasWebSearch
{
// Web Search 工具映射
return
[]
GeminiToolDeclaration
{{
GoogleSearch
:
&
GeminiGoogleSearch
{
EnhancedContent
:
&
GeminiEnhancedContent
{
ImageSearch
:
&
GeminiImageSearch
{
MaxResultCount
:
5
,
},
},
},
}}
}
// 普通工具
var
funcDecls
[]
GeminiFunctionDecl
for
_
,
tool
:=
range
tools
{
// 清理 JSON Schema
params
:=
cleanJSONSchema
(
tool
.
InputSchema
)
funcDecls
=
append
(
funcDecls
,
GeminiFunctionDecl
{
Name
:
tool
.
Name
,
Description
:
tool
.
Description
,
Parameters
:
params
,
})
}
if
len
(
funcDecls
)
==
0
{
return
nil
}
return
[]
GeminiToolDeclaration
{{
FunctionDeclarations
:
funcDecls
,
}}
}
// cleanJSONSchema 清理 JSON Schema,移除 Gemini 不支持的字段
func
cleanJSONSchema
(
schema
map
[
string
]
interface
{})
map
[
string
]
interface
{}
{
if
schema
==
nil
{
return
nil
}
result
:=
make
(
map
[
string
]
interface
{})
for
k
,
v
:=
range
schema
{
// 移除不支持的字段
switch
k
{
case
"$schema"
,
"additionalProperties"
,
"minLength"
,
"maxLength"
,
"minimum"
,
"maximum"
,
"exclusiveMinimum"
,
"exclusiveMaximum"
,
"pattern"
,
"format"
,
"default"
:
continue
}
// 递归处理嵌套对象
if
nested
,
ok
:=
v
.
(
map
[
string
]
interface
{});
ok
{
result
[
k
]
=
cleanJSONSchema
(
nested
)
}
else
if
k
==
"type"
{
// 处理类型字段,转换为大写
if
typeStr
,
ok
:=
v
.
(
string
);
ok
{
result
[
k
]
=
strings
.
ToUpper
(
typeStr
)
}
else
if
typeArr
,
ok
:=
v
.
([]
interface
{});
ok
{
// 处理联合类型 ["string", "null"] -> "STRING"
for
_
,
t
:=
range
typeArr
{
if
ts
,
ok
:=
t
.
(
string
);
ok
&&
ts
!=
"null"
{
result
[
k
]
=
strings
.
ToUpper
(
ts
)
break
}
}
}
else
{
result
[
k
]
=
v
}
}
else
{
result
[
k
]
=
v
}
}
// 递归处理 properties
if
props
,
ok
:=
result
[
"properties"
]
.
(
map
[
string
]
interface
{});
ok
{
cleanedProps
:=
make
(
map
[
string
]
interface
{})
for
name
,
prop
:=
range
props
{
if
propMap
,
ok
:=
prop
.
(
map
[
string
]
interface
{});
ok
{
cleanedProps
[
name
]
=
cleanJSONSchema
(
propMap
)
}
else
{
cleanedProps
[
name
]
=
prop
}
}
result
[
"properties"
]
=
cleanedProps
}
return
result
}
backend/internal/pkg/antigravity/response_transformer.go
0 → 100644
View file @
b0389ca4
package
antigravity
import
(
"encoding/json"
"fmt"
)
// TransformGeminiToClaude 将 Gemini 响应转换为 Claude 格式(非流式)
func
TransformGeminiToClaude
(
geminiResp
[]
byte
,
originalModel
string
)
([]
byte
,
*
ClaudeUsage
,
error
)
{
// 解包 v1internal 响应
var
v1Resp
V1InternalResponse
if
err
:=
json
.
Unmarshal
(
geminiResp
,
&
v1Resp
);
err
!=
nil
{
// 尝试直接解析为 GeminiResponse
var
directResp
GeminiResponse
if
err2
:=
json
.
Unmarshal
(
geminiResp
,
&
directResp
);
err2
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"parse gemini response: %w"
,
err
)
}
v1Resp
.
Response
=
directResp
v1Resp
.
ResponseID
=
directResp
.
ResponseID
v1Resp
.
ModelVersion
=
directResp
.
ModelVersion
}
// 使用处理器转换
processor
:=
NewNonStreamingProcessor
()
claudeResp
:=
processor
.
Process
(
&
v1Resp
.
Response
,
v1Resp
.
ResponseID
,
originalModel
)
// 序列化
respBytes
,
err
:=
json
.
Marshal
(
claudeResp
)
if
err
!=
nil
{
return
nil
,
nil
,
fmt
.
Errorf
(
"marshal claude response: %w"
,
err
)
}
return
respBytes
,
&
claudeResp
.
Usage
,
nil
}
// NonStreamingProcessor 非流式响应处理器
type
NonStreamingProcessor
struct
{
contentBlocks
[]
ClaudeContentItem
textBuilder
string
thinkingBuilder
string
thinkingSignature
string
trailingSignature
string
hasToolCall
bool
}
// NewNonStreamingProcessor 创建非流式响应处理器
func
NewNonStreamingProcessor
()
*
NonStreamingProcessor
{
return
&
NonStreamingProcessor
{
contentBlocks
:
make
([]
ClaudeContentItem
,
0
),
}
}
// Process 处理 Gemini 响应
func
(
p
*
NonStreamingProcessor
)
Process
(
geminiResp
*
GeminiResponse
,
responseID
,
originalModel
string
)
*
ClaudeResponse
{
// 获取 parts
var
parts
[]
GeminiPart
if
len
(
geminiResp
.
Candidates
)
>
0
&&
geminiResp
.
Candidates
[
0
]
.
Content
!=
nil
{
parts
=
geminiResp
.
Candidates
[
0
]
.
Content
.
Parts
}
// 处理所有 parts
for
_
,
part
:=
range
parts
{
p
.
processPart
(
&
part
)
}
// 刷新剩余内容
p
.
flushThinking
()
p
.
flushText
()
// 处理 trailingSignature
if
p
.
trailingSignature
!=
""
{
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
""
,
Signature
:
p
.
trailingSignature
,
})
}
// 构建响应
return
p
.
buildResponse
(
geminiResp
,
responseID
,
originalModel
)
}
// processPart 处理单个 part
func
(
p
*
NonStreamingProcessor
)
processPart
(
part
*
GeminiPart
)
{
signature
:=
part
.
ThoughtSignature
// 1. FunctionCall 处理
if
part
.
FunctionCall
!=
nil
{
p
.
flushThinking
()
p
.
flushText
()
// 处理 trailingSignature
if
p
.
trailingSignature
!=
""
{
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
""
,
Signature
:
p
.
trailingSignature
,
})
p
.
trailingSignature
=
""
}
p
.
hasToolCall
=
true
// 生成 tool_use id
toolID
:=
part
.
FunctionCall
.
ID
if
toolID
==
""
{
toolID
=
fmt
.
Sprintf
(
"%s-%s"
,
part
.
FunctionCall
.
Name
,
generateRandomID
())
}
item
:=
ClaudeContentItem
{
Type
:
"tool_use"
,
ID
:
toolID
,
Name
:
part
.
FunctionCall
.
Name
,
Input
:
part
.
FunctionCall
.
Args
,
}
if
signature
!=
""
{
item
.
Signature
=
signature
}
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
item
)
return
}
// 2. Text 处理
if
part
.
Text
!=
""
||
part
.
Thought
{
if
part
.
Thought
{
// Thinking part
p
.
flushText
()
// 处理 trailingSignature
if
p
.
trailingSignature
!=
""
{
p
.
flushThinking
()
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
""
,
Signature
:
p
.
trailingSignature
,
})
p
.
trailingSignature
=
""
}
p
.
thinkingBuilder
+=
part
.
Text
if
signature
!=
""
{
p
.
thinkingSignature
=
signature
}
}
else
{
// 普通 Text
if
part
.
Text
==
""
{
// 空 text 带签名 - 暂存
if
signature
!=
""
{
p
.
trailingSignature
=
signature
}
return
}
p
.
flushThinking
()
// 处理之前的 trailingSignature
if
p
.
trailingSignature
!=
""
{
p
.
flushText
()
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
""
,
Signature
:
p
.
trailingSignature
,
})
p
.
trailingSignature
=
""
}
p
.
textBuilder
+=
part
.
Text
// 非空 text 带签名 - 立即刷新并输出空 thinking 块
if
signature
!=
""
{
p
.
flushText
()
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
""
,
Signature
:
signature
,
})
}
}
}
// 3. InlineData (Image) 处理
if
part
.
InlineData
!=
nil
&&
part
.
InlineData
.
Data
!=
""
{
p
.
flushThinking
()
markdownImg
:=
fmt
.
Sprintf
(
""
,
part
.
InlineData
.
MimeType
,
part
.
InlineData
.
Data
)
p
.
textBuilder
+=
markdownImg
p
.
flushText
()
}
}
// flushText 刷新 text builder
func
(
p
*
NonStreamingProcessor
)
flushText
()
{
if
p
.
textBuilder
==
""
{
return
}
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"text"
,
Text
:
p
.
textBuilder
,
})
p
.
textBuilder
=
""
}
// flushThinking 刷新 thinking builder
func
(
p
*
NonStreamingProcessor
)
flushThinking
()
{
if
p
.
thinkingBuilder
==
""
&&
p
.
thinkingSignature
==
""
{
return
}
p
.
contentBlocks
=
append
(
p
.
contentBlocks
,
ClaudeContentItem
{
Type
:
"thinking"
,
Thinking
:
p
.
thinkingBuilder
,
Signature
:
p
.
thinkingSignature
,
})
p
.
thinkingBuilder
=
""
p
.
thinkingSignature
=
""
}
// buildResponse 构建最终响应
func
(
p
*
NonStreamingProcessor
)
buildResponse
(
geminiResp
*
GeminiResponse
,
responseID
,
originalModel
string
)
*
ClaudeResponse
{
var
finishReason
string
if
len
(
geminiResp
.
Candidates
)
>
0
{
finishReason
=
geminiResp
.
Candidates
[
0
]
.
FinishReason
}
stopReason
:=
"end_turn"
if
p
.
hasToolCall
{
stopReason
=
"tool_use"
}
else
if
finishReason
==
"MAX_TOKENS"
{
stopReason
=
"max_tokens"
}
usage
:=
ClaudeUsage
{}
if
geminiResp
.
UsageMetadata
!=
nil
{
usage
.
InputTokens
=
geminiResp
.
UsageMetadata
.
PromptTokenCount
usage
.
OutputTokens
=
geminiResp
.
UsageMetadata
.
CandidatesTokenCount
}
// 生成响应 ID
respID
:=
responseID
if
respID
==
""
{
respID
=
geminiResp
.
ResponseID
}
if
respID
==
""
{
respID
=
"msg_"
+
generateRandomID
()
}
return
&
ClaudeResponse
{
ID
:
respID
,
Type
:
"message"
,
Role
:
"assistant"
,
Model
:
originalModel
,
Content
:
p
.
contentBlocks
,
StopReason
:
stopReason
,
Usage
:
usage
,
}
}
// generateRandomID 生成随机 ID
func
generateRandomID
()
string
{
const
chars
=
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
result
:=
make
([]
byte
,
12
)
for
i
:=
range
result
{
result
[
i
]
=
chars
[
i
%
len
(
chars
)]
}
return
string
(
result
)
}
backend/internal/pkg/antigravity/stream_transformer.go
0 → 100644
View file @
b0389ca4
package
antigravity
import
(
"bytes"
"encoding/json"
"fmt"
"strings"
)
// BlockType 内容块类型
type
BlockType
int
const
(
BlockTypeNone
BlockType
=
iota
BlockTypeText
BlockTypeThinking
BlockTypeFunction
)
// StreamingProcessor 流式响应处理器
type
StreamingProcessor
struct
{
blockType
BlockType
blockIndex
int
messageStartSent
bool
messageStopSent
bool
usedTool
bool
pendingSignature
string
trailingSignature
string
originalModel
string
// 累计 usage
inputTokens
int
outputTokens
int
}
// NewStreamingProcessor 创建流式响应处理器
func
NewStreamingProcessor
(
originalModel
string
)
*
StreamingProcessor
{
return
&
StreamingProcessor
{
blockType
:
BlockTypeNone
,
originalModel
:
originalModel
,
}
}
// ProcessLine 处理 SSE 行,返回 Claude SSE 事件
func
(
p
*
StreamingProcessor
)
ProcessLine
(
line
string
)
[]
byte
{
line
=
strings
.
TrimSpace
(
line
)
if
line
==
""
||
!
strings
.
HasPrefix
(
line
,
"data:"
)
{
return
nil
}
data
:=
strings
.
TrimSpace
(
strings
.
TrimPrefix
(
line
,
"data:"
))
if
data
==
""
||
data
==
"[DONE]"
{
return
nil
}
// 解包 v1internal 响应
var
v1Resp
V1InternalResponse
if
err
:=
json
.
Unmarshal
([]
byte
(
data
),
&
v1Resp
);
err
!=
nil
{
// 尝试直接解析为 GeminiResponse
var
directResp
GeminiResponse
if
err2
:=
json
.
Unmarshal
([]
byte
(
data
),
&
directResp
);
err2
!=
nil
{
return
nil
}
v1Resp
.
Response
=
directResp
v1Resp
.
ResponseID
=
directResp
.
ResponseID
v1Resp
.
ModelVersion
=
directResp
.
ModelVersion
}
geminiResp
:=
&
v1Resp
.
Response
var
result
bytes
.
Buffer
// 发送 message_start
if
!
p
.
messageStartSent
{
result
.
Write
(
p
.
emitMessageStart
(
&
v1Resp
))
}
// 更新 usage
if
geminiResp
.
UsageMetadata
!=
nil
{
p
.
inputTokens
=
geminiResp
.
UsageMetadata
.
PromptTokenCount
p
.
outputTokens
=
geminiResp
.
UsageMetadata
.
CandidatesTokenCount
}
// 处理 parts
if
len
(
geminiResp
.
Candidates
)
>
0
&&
geminiResp
.
Candidates
[
0
]
.
Content
!=
nil
{
for
_
,
part
:=
range
geminiResp
.
Candidates
[
0
]
.
Content
.
Parts
{
result
.
Write
(
p
.
processPart
(
&
part
))
}
}
// 检查是否结束
if
len
(
geminiResp
.
Candidates
)
>
0
{
finishReason
:=
geminiResp
.
Candidates
[
0
]
.
FinishReason
if
finishReason
!=
""
{
result
.
Write
(
p
.
emitFinish
(
finishReason
))
}
}
return
result
.
Bytes
()
}
// Finish 结束处理,返回最终事件和用量
func
(
p
*
StreamingProcessor
)
Finish
()
([]
byte
,
*
ClaudeUsage
)
{
var
result
bytes
.
Buffer
if
!
p
.
messageStopSent
{
result
.
Write
(
p
.
emitFinish
(
""
))
}
usage
:=
&
ClaudeUsage
{
InputTokens
:
p
.
inputTokens
,
OutputTokens
:
p
.
outputTokens
,
}
return
result
.
Bytes
(),
usage
}
// emitMessageStart 发送 message_start 事件
func
(
p
*
StreamingProcessor
)
emitMessageStart
(
v1Resp
*
V1InternalResponse
)
[]
byte
{
if
p
.
messageStartSent
{
return
nil
}
usage
:=
ClaudeUsage
{}
if
v1Resp
.
Response
.
UsageMetadata
!=
nil
{
usage
.
InputTokens
=
v1Resp
.
Response
.
UsageMetadata
.
PromptTokenCount
usage
.
OutputTokens
=
v1Resp
.
Response
.
UsageMetadata
.
CandidatesTokenCount
}
responseID
:=
v1Resp
.
ResponseID
if
responseID
==
""
{
responseID
=
v1Resp
.
Response
.
ResponseID
}
if
responseID
==
""
{
responseID
=
"msg_"
+
generateRandomID
()
}
message
:=
map
[
string
]
interface
{}{
"id"
:
responseID
,
"type"
:
"message"
,
"role"
:
"assistant"
,
"content"
:
[]
interface
{}{},
"model"
:
p
.
originalModel
,
"stop_reason"
:
nil
,
"stop_sequence"
:
nil
,
"usage"
:
usage
,
}
event
:=
map
[
string
]
interface
{}{
"type"
:
"message_start"
,
"message"
:
message
,
}
p
.
messageStartSent
=
true
return
p
.
formatSSE
(
"message_start"
,
event
)
}
// processPart 处理单个 part
func
(
p
*
StreamingProcessor
)
processPart
(
part
*
GeminiPart
)
[]
byte
{
var
result
bytes
.
Buffer
signature
:=
part
.
ThoughtSignature
// 1. FunctionCall 处理
if
part
.
FunctionCall
!=
nil
{
// 先处理 trailingSignature
if
p
.
trailingSignature
!=
""
{
result
.
Write
(
p
.
endBlock
())
result
.
Write
(
p
.
emitEmptyThinkingWithSignature
(
p
.
trailingSignature
))
p
.
trailingSignature
=
""
}
result
.
Write
(
p
.
processFunctionCall
(
part
.
FunctionCall
,
signature
))
return
result
.
Bytes
()
}
// 2. Text 处理
if
part
.
Text
!=
""
||
part
.
Thought
{
if
part
.
Thought
{
result
.
Write
(
p
.
processThinking
(
part
.
Text
,
signature
))
}
else
{
result
.
Write
(
p
.
processText
(
part
.
Text
,
signature
))
}
}
// 3. InlineData (Image) 处理
if
part
.
InlineData
!=
nil
&&
part
.
InlineData
.
Data
!=
""
{
markdownImg
:=
fmt
.
Sprintf
(
""
,
part
.
InlineData
.
MimeType
,
part
.
InlineData
.
Data
)
result
.
Write
(
p
.
processText
(
markdownImg
,
""
))
}
return
result
.
Bytes
()
}
// processThinking 处理 thinking
func
(
p
*
StreamingProcessor
)
processThinking
(
text
,
signature
string
)
[]
byte
{
var
result
bytes
.
Buffer
// 处理之前的 trailingSignature
if
p
.
trailingSignature
!=
""
{
result
.
Write
(
p
.
endBlock
())
result
.
Write
(
p
.
emitEmptyThinkingWithSignature
(
p
.
trailingSignature
))
p
.
trailingSignature
=
""
}
// 开始或继续 thinking 块
if
p
.
blockType
!=
BlockTypeThinking
{
result
.
Write
(
p
.
startBlock
(
BlockTypeThinking
,
map
[
string
]
interface
{}{
"type"
:
"thinking"
,
"thinking"
:
""
,
}))
}
if
text
!=
""
{
result
.
Write
(
p
.
emitDelta
(
"thinking_delta"
,
map
[
string
]
interface
{}{
"thinking"
:
text
,
}))
}
// 暂存签名
if
signature
!=
""
{
p
.
pendingSignature
=
signature
}
return
result
.
Bytes
()
}
// processText 处理普通 text
func
(
p
*
StreamingProcessor
)
processText
(
text
,
signature
string
)
[]
byte
{
var
result
bytes
.
Buffer
// 空 text 带签名 - 暂存
if
text
==
""
{
if
signature
!=
""
{
p
.
trailingSignature
=
signature
}
return
nil
}
// 处理之前的 trailingSignature
if
p
.
trailingSignature
!=
""
{
result
.
Write
(
p
.
endBlock
())
result
.
Write
(
p
.
emitEmptyThinkingWithSignature
(
p
.
trailingSignature
))
p
.
trailingSignature
=
""
}
// 非空 text 带签名 - 特殊处理
if
signature
!=
""
{
result
.
Write
(
p
.
startBlock
(
BlockTypeText
,
map
[
string
]
interface
{}{
"type"
:
"text"
,
"text"
:
""
,
}))
result
.
Write
(
p
.
emitDelta
(
"text_delta"
,
map
[
string
]
interface
{}{
"text"
:
text
,
}))
result
.
Write
(
p
.
endBlock
())
result
.
Write
(
p
.
emitEmptyThinkingWithSignature
(
signature
))
return
result
.
Bytes
()
}
// 普通 text (无签名)
if
p
.
blockType
!=
BlockTypeText
{
result
.
Write
(
p
.
startBlock
(
BlockTypeText
,
map
[
string
]
interface
{}{
"type"
:
"text"
,
"text"
:
""
,
}))
}
result
.
Write
(
p
.
emitDelta
(
"text_delta"
,
map
[
string
]
interface
{}{
"text"
:
text
,
}))
return
result
.
Bytes
()
}
// processFunctionCall 处理 function call
func
(
p
*
StreamingProcessor
)
processFunctionCall
(
fc
*
GeminiFunctionCall
,
signature
string
)
[]
byte
{
var
result
bytes
.
Buffer
p
.
usedTool
=
true
toolID
:=
fc
.
ID
if
toolID
==
""
{
toolID
=
fmt
.
Sprintf
(
"%s-%s"
,
fc
.
Name
,
generateRandomID
())
}
toolUse
:=
map
[
string
]
interface
{}{
"type"
:
"tool_use"
,
"id"
:
toolID
,
"name"
:
fc
.
Name
,
"input"
:
map
[
string
]
interface
{}{},
// 必须为空,参数通过 delta 发送
}
if
signature
!=
""
{
toolUse
[
"signature"
]
=
signature
}
result
.
Write
(
p
.
startBlock
(
BlockTypeFunction
,
toolUse
))
// 发送 input_json_delta
if
fc
.
Args
!=
nil
{
argsJSON
,
_
:=
json
.
Marshal
(
fc
.
Args
)
result
.
Write
(
p
.
emitDelta
(
"input_json_delta"
,
map
[
string
]
interface
{}{
"partial_json"
:
string
(
argsJSON
),
}))
}
result
.
Write
(
p
.
endBlock
())
return
result
.
Bytes
()
}
// startBlock 开始新的内容块
func
(
p
*
StreamingProcessor
)
startBlock
(
blockType
BlockType
,
contentBlock
map
[
string
]
interface
{})
[]
byte
{
var
result
bytes
.
Buffer
if
p
.
blockType
!=
BlockTypeNone
{
result
.
Write
(
p
.
endBlock
())
}
event
:=
map
[
string
]
interface
{}{
"type"
:
"content_block_start"
,
"index"
:
p
.
blockIndex
,
"content_block"
:
contentBlock
,
}
result
.
Write
(
p
.
formatSSE
(
"content_block_start"
,
event
))
p
.
blockType
=
blockType
return
result
.
Bytes
()
}
// endBlock 结束当前内容块
func
(
p
*
StreamingProcessor
)
endBlock
()
[]
byte
{
if
p
.
blockType
==
BlockTypeNone
{
return
nil
}
var
result
bytes
.
Buffer
// Thinking 块结束时发送暂存的签名
if
p
.
blockType
==
BlockTypeThinking
&&
p
.
pendingSignature
!=
""
{
result
.
Write
(
p
.
emitDelta
(
"signature_delta"
,
map
[
string
]
interface
{}{
"signature"
:
p
.
pendingSignature
,
}))
p
.
pendingSignature
=
""
}
event
:=
map
[
string
]
interface
{}{
"type"
:
"content_block_stop"
,
"index"
:
p
.
blockIndex
,
}
result
.
Write
(
p
.
formatSSE
(
"content_block_stop"
,
event
))
p
.
blockIndex
++
p
.
blockType
=
BlockTypeNone
return
result
.
Bytes
()
}
// emitDelta 发送 delta 事件
func
(
p
*
StreamingProcessor
)
emitDelta
(
deltaType
string
,
deltaContent
map
[
string
]
interface
{})
[]
byte
{
delta
:=
map
[
string
]
interface
{}{
"type"
:
deltaType
,
}
for
k
,
v
:=
range
deltaContent
{
delta
[
k
]
=
v
}
event
:=
map
[
string
]
interface
{}{
"type"
:
"content_block_delta"
,
"index"
:
p
.
blockIndex
,
"delta"
:
delta
,
}
return
p
.
formatSSE
(
"content_block_delta"
,
event
)
}
// emitEmptyThinkingWithSignature 发送空 thinking 块承载签名
func
(
p
*
StreamingProcessor
)
emitEmptyThinkingWithSignature
(
signature
string
)
[]
byte
{
var
result
bytes
.
Buffer
result
.
Write
(
p
.
startBlock
(
BlockTypeThinking
,
map
[
string
]
interface
{}{
"type"
:
"thinking"
,
"thinking"
:
""
,
}))
result
.
Write
(
p
.
emitDelta
(
"thinking_delta"
,
map
[
string
]
interface
{}{
"thinking"
:
""
,
}))
result
.
Write
(
p
.
emitDelta
(
"signature_delta"
,
map
[
string
]
interface
{}{
"signature"
:
signature
,
}))
result
.
Write
(
p
.
endBlock
())
return
result
.
Bytes
()
}
// emitFinish 发送结束事件
func
(
p
*
StreamingProcessor
)
emitFinish
(
finishReason
string
)
[]
byte
{
var
result
bytes
.
Buffer
// 关闭最后一个块
result
.
Write
(
p
.
endBlock
())
// 处理 trailingSignature
if
p
.
trailingSignature
!=
""
{
result
.
Write
(
p
.
emitEmptyThinkingWithSignature
(
p
.
trailingSignature
))
p
.
trailingSignature
=
""
}
// 确定 stop_reason
stopReason
:=
"end_turn"
if
p
.
usedTool
{
stopReason
=
"tool_use"
}
else
if
finishReason
==
"MAX_TOKENS"
{
stopReason
=
"max_tokens"
}
usage
:=
ClaudeUsage
{
InputTokens
:
p
.
inputTokens
,
OutputTokens
:
p
.
outputTokens
,
}
deltaEvent
:=
map
[
string
]
interface
{}{
"type"
:
"message_delta"
,
"delta"
:
map
[
string
]
interface
{}{
"stop_reason"
:
stopReason
,
"stop_sequence"
:
nil
,
},
"usage"
:
usage
,
}
result
.
Write
(
p
.
formatSSE
(
"message_delta"
,
deltaEvent
))
if
!
p
.
messageStopSent
{
stopEvent
:=
map
[
string
]
interface
{}{
"type"
:
"message_stop"
,
}
result
.
Write
(
p
.
formatSSE
(
"message_stop"
,
stopEvent
))
p
.
messageStopSent
=
true
}
return
result
.
Bytes
()
}
// formatSSE 格式化 SSE 事件
func
(
p
*
StreamingProcessor
)
formatSSE
(
eventType
string
,
data
interface
{})
[]
byte
{
jsonData
,
err
:=
json
.
Marshal
(
data
)
if
err
!=
nil
{
return
nil
}
return
[]
byte
(
fmt
.
Sprintf
(
"event: %s
\n
data: %s
\n\n
"
,
eventType
,
string
(
jsonData
)))
}
backend/internal/service/antigravity_gateway_service.go
View file @
b0389ca4
...
...
@@ -47,9 +47,10 @@ var antigravityModelMapping = map[string]string{
"claude-sonnet-4-5-20250929"
:
"claude-sonnet-4-5-thinking"
,
"claude-opus-4"
:
"claude-opus-4-5-thinking"
,
"claude-opus-4-5-20251101"
:
"claude-opus-4-5-thinking"
,
"claude-haiku-4"
:
"claude-sonnet-4-5"
,
"claude-3-haiku-20240307"
:
"claude-sonnet-4-5"
,
"claude-haiku-4-5-20251001"
:
"claude-sonnet-4-5"
,
"claude-haiku-4"
:
"gemini-3-flash"
,
"claude-haiku-4-5"
:
"gemini-3-flash"
,
"claude-3-haiku-20240307"
:
"gemini-3-flash"
,
"claude-haiku-4-5-20251001"
:
"gemini-3-flash"
,
}
// AntigravityGatewayService 处理 Antigravity 平台的 API 转发
...
...
@@ -189,26 +190,23 @@ func (s *AntigravityGatewayService) unwrapSSELine(line string) string {
return
line
}
// Forward 转发 Claude 协议请求
// Forward 转发 Claude 协议请求
(Claude → Gemini 转换)
func
(
s
*
AntigravityGatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
body
[]
byte
)
(
*
ForwardResult
,
error
)
{
startTime
:=
time
.
Now
()
// 解析
请求获取 model 和 stream
var
req
struct
{
Model
string
`json:"model"`
Stream
bool
`json:"stream"`
// 解析
Claude 请求
var
claudeReq
antigravity
.
ClaudeRequest
if
err
:=
json
.
Unmarshal
(
body
,
&
claudeReq
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"parse claude request: %w"
,
err
)
}
if
err
:=
json
.
Unmarshal
(
body
,
&
req
);
err
!=
nil
{
return
nil
,
fmt
.
Errorf
(
"parse request: %w"
,
err
)
}
if
strings
.
TrimSpace
(
req
.
Model
)
==
""
{
if
strings
.
TrimSpace
(
claudeReq
.
Model
)
==
""
{
return
nil
,
fmt
.
Errorf
(
"missing model"
)
}
originalModel
:=
r
eq
.
Model
mappedModel
:=
s
.
getMappedModel
(
account
,
r
eq
.
Model
)
if
mappedModel
!=
r
eq
.
Model
{
log
.
Printf
(
"Antigravity model mapping: %s -> %s (account: %s)"
,
r
eq
.
Model
,
mappedModel
,
account
.
Name
)
originalModel
:=
claudeR
eq
.
Model
mappedModel
:=
s
.
getMappedModel
(
account
,
claudeR
eq
.
Model
)
if
mappedModel
!=
claudeR
eq
.
Model
{
log
.
Printf
(
"Antigravity model mapping: %s -> %s (account: %s)"
,
claudeR
eq
.
Model
,
mappedModel
,
account
.
Name
)
}
// 获取 access_token
...
...
@@ -232,26 +230,26 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
proxyURL
=
account
.
Proxy
.
URL
()
}
//
包装请求
wrapped
Body
,
err
:=
s
.
wrapV1InternalRequest
(
projectID
,
mappedModel
,
body
)
//
转换 Claude 请求为 Gemini 格式
gemini
Body
,
err
:=
antigravity
.
TransformClaudeToGemini
(
&
claudeReq
,
projectID
,
mappedModel
)
if
err
!=
nil
{
return
nil
,
err
return
nil
,
fmt
.
Errorf
(
"transform request: %w"
,
err
)
}
// 构建上游 URL
action
:=
"generateContent"
if
r
eq
.
Stream
{
if
claudeR
eq
.
Stream
{
action
=
"streamGenerateContent"
}
fullURL
:=
fmt
.
Sprintf
(
"%s/v1internal:%s"
,
antigravity
.
BaseURL
,
action
)
if
r
eq
.
Stream
{
if
claudeR
eq
.
Stream
{
fullURL
+=
"?alt=sse"
}
// 重试循环
var
resp
*
http
.
Response
for
attempt
:=
1
;
attempt
<=
antigravityMaxRetries
;
attempt
++
{
upstreamReq
,
err
:=
http
.
NewRequestWithContext
(
ctx
,
http
.
MethodPost
,
fullURL
,
bytes
.
NewReader
(
wrapped
Body
))
upstreamReq
,
err
:=
http
.
NewRequestWithContext
(
ctx
,
http
.
MethodPost
,
fullURL
,
bytes
.
NewReader
(
gemini
Body
))
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -313,15 +311,15 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
var
usage
*
ClaudeUsage
var
firstTokenMs
*
int
if
r
eq
.
Stream
{
streamRes
,
err
:=
s
.
handleStreamingResponse
(
c
,
resp
,
startTime
,
originalModel
)
if
claudeR
eq
.
Stream
{
streamRes
,
err
:=
s
.
handle
Claude
StreamingResponse
(
c
,
resp
,
startTime
,
originalModel
)
if
err
!=
nil
{
return
nil
,
err
}
usage
=
streamRes
.
usage
firstTokenMs
=
streamRes
.
firstTokenMs
}
else
{
usage
,
err
=
s
.
handleNonStreamingResponse
(
c
,
resp
,
originalModel
)
usage
,
err
=
s
.
handle
Claude
NonStreamingResponse
(
c
,
resp
,
originalModel
)
if
err
!=
nil
{
return
nil
,
err
}
...
...
@@ -331,7 +329,7 @@ func (s *AntigravityGatewayService) Forward(ctx context.Context, c *gin.Context,
RequestID
:
requestID
,
Usage
:
*
usage
,
Model
:
originalModel
,
// 使用原始模型用于计费和日志
Stream
:
r
eq
.
Stream
,
Stream
:
claudeR
eq
.
Stream
,
Duration
:
time
.
Since
(
startTime
),
FirstTokenMs
:
firstTokenMs
,
},
nil
...
...
@@ -782,6 +780,9 @@ func (s *AntigravityGatewayService) writeClaudeError(c *gin.Context, status int,
}
func
(
s
*
AntigravityGatewayService
)
writeMappedClaudeError
(
c
*
gin
.
Context
,
upstreamStatus
int
,
body
[]
byte
)
error
{
// 记录上游错误详情便于调试
log
.
Printf
(
"Antigravity upstream error %d: %s"
,
upstreamStatus
,
string
(
body
))
var
statusCode
int
var
errType
,
errMsg
string
...
...
@@ -843,3 +844,101 @@ func (s *AntigravityGatewayService) writeGoogleError(c *gin.Context, status int,
})
return
fmt
.
Errorf
(
"%s"
,
message
)
}
// handleClaudeNonStreamingResponse 处理 Claude 非流式响应(Gemini → Claude 转换)
func
(
s
*
AntigravityGatewayService
)
handleClaudeNonStreamingResponse
(
c
*
gin
.
Context
,
resp
*
http
.
Response
,
originalModel
string
)
(
*
ClaudeUsage
,
error
)
{
body
,
err
:=
io
.
ReadAll
(
io
.
LimitReader
(
resp
.
Body
,
8
<<
20
))
if
err
!=
nil
{
return
nil
,
s
.
writeClaudeError
(
c
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Failed to read upstream response"
)
}
// 转换 Gemini 响应为 Claude 格式
claudeResp
,
agUsage
,
err
:=
antigravity
.
TransformGeminiToClaude
(
body
,
originalModel
)
if
err
!=
nil
{
log
.
Printf
(
"Transform Gemini to Claude failed: %v, body: %s"
,
err
,
string
(
body
))
return
nil
,
s
.
writeClaudeError
(
c
,
http
.
StatusBadGateway
,
"upstream_error"
,
"Failed to parse upstream response"
)
}
c
.
Data
(
http
.
StatusOK
,
"application/json"
,
claudeResp
)
// 转换为 service.ClaudeUsage
usage
:=
&
ClaudeUsage
{
InputTokens
:
agUsage
.
InputTokens
,
OutputTokens
:
agUsage
.
OutputTokens
,
CacheCreationInputTokens
:
agUsage
.
CacheCreationInputTokens
,
CacheReadInputTokens
:
agUsage
.
CacheReadInputTokens
,
}
return
usage
,
nil
}
// handleClaudeStreamingResponse 处理 Claude 流式响应(Gemini SSE → Claude SSE 转换)
func
(
s
*
AntigravityGatewayService
)
handleClaudeStreamingResponse
(
c
*
gin
.
Context
,
resp
*
http
.
Response
,
startTime
time
.
Time
,
originalModel
string
)
(
*
antigravityStreamResult
,
error
)
{
c
.
Header
(
"Content-Type"
,
"text/event-stream"
)
c
.
Header
(
"Cache-Control"
,
"no-cache"
)
c
.
Header
(
"Connection"
,
"keep-alive"
)
c
.
Header
(
"X-Accel-Buffering"
,
"no"
)
c
.
Status
(
http
.
StatusOK
)
flusher
,
ok
:=
c
.
Writer
.
(
http
.
Flusher
)
if
!
ok
{
return
nil
,
errors
.
New
(
"streaming not supported"
)
}
processor
:=
antigravity
.
NewStreamingProcessor
(
originalModel
)
var
firstTokenMs
*
int
reader
:=
bufio
.
NewReader
(
resp
.
Body
)
// 辅助函数:转换 antigravity.ClaudeUsage 到 service.ClaudeUsage
convertUsage
:=
func
(
agUsage
*
antigravity
.
ClaudeUsage
)
*
ClaudeUsage
{
if
agUsage
==
nil
{
return
&
ClaudeUsage
{}
}
return
&
ClaudeUsage
{
InputTokens
:
agUsage
.
InputTokens
,
OutputTokens
:
agUsage
.
OutputTokens
,
CacheCreationInputTokens
:
agUsage
.
CacheCreationInputTokens
,
CacheReadInputTokens
:
agUsage
.
CacheReadInputTokens
,
}
}
for
{
line
,
err
:=
reader
.
ReadString
(
'\n'
)
if
err
!=
nil
&&
!
errors
.
Is
(
err
,
io
.
EOF
)
{
return
nil
,
fmt
.
Errorf
(
"stream read error: %w"
,
err
)
}
if
len
(
line
)
>
0
{
// 处理 SSE 行,转换为 Claude 格式
claudeEvents
:=
processor
.
ProcessLine
(
strings
.
TrimRight
(
line
,
"
\r\n
"
))
if
len
(
claudeEvents
)
>
0
{
if
firstTokenMs
==
nil
{
ms
:=
int
(
time
.
Since
(
startTime
)
.
Milliseconds
())
firstTokenMs
=
&
ms
}
if
_
,
writeErr
:=
c
.
Writer
.
Write
(
claudeEvents
);
writeErr
!=
nil
{
finalEvents
,
agUsage
:=
processor
.
Finish
()
if
len
(
finalEvents
)
>
0
{
_
,
_
=
c
.
Writer
.
Write
(
finalEvents
)
}
return
&
antigravityStreamResult
{
usage
:
convertUsage
(
agUsage
),
firstTokenMs
:
firstTokenMs
},
writeErr
}
flusher
.
Flush
()
}
}
if
errors
.
Is
(
err
,
io
.
EOF
)
{
break
}
}
// 发送结束事件
finalEvents
,
agUsage
:=
processor
.
Finish
()
if
len
(
finalEvents
)
>
0
{
_
,
_
=
c
.
Writer
.
Write
(
finalEvents
)
flusher
.
Flush
()
}
return
&
antigravityStreamResult
{
usage
:
convertUsage
(
agUsage
),
firstTokenMs
:
firstTokenMs
},
nil
}
backend/internal/service/antigravity_model_mapping_test.go
View file @
b0389ca4
...
...
@@ -104,16 +104,28 @@ func TestAntigravityGatewayService_GetMappedModel(t *testing.T) {
expected
:
"claude-opus-4-5-thinking"
,
},
{
name
:
"系统映射 - claude-haiku-4"
,
name
:
"系统映射 - claude-haiku-4
→ gemini-3-flash
"
,
requestedModel
:
"claude-haiku-4"
,
accountMapping
:
nil
,
expected
:
"claude-sonnet-4-5"
,
expected
:
"gemini-3-flash"
,
},
{
name
:
"系统映射 - claude-haiku-4-5 → gemini-3-flash"
,
requestedModel
:
"claude-haiku-4-5"
,
accountMapping
:
nil
,
expected
:
"gemini-3-flash"
,
},
{
name
:
"系统映射 - claude-3-haiku-20240307"
,
name
:
"系统映射 - claude-3-haiku-20240307
→ gemini-3-flash
"
,
requestedModel
:
"claude-3-haiku-20240307"
,
accountMapping
:
nil
,
expected
:
"claude-sonnet-4-5"
,
expected
:
"gemini-3-flash"
,
},
{
name
:
"系统映射 - claude-haiku-4-5-20251001 → gemini-3-flash"
,
requestedModel
:
"claude-haiku-4-5-20251001"
,
accountMapping
:
nil
,
expected
:
"gemini-3-flash"
,
},
{
name
:
"系统映射 - claude-sonnet-4-5-20250929"
,
...
...
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