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
d9b15879
"backend/internal/git@web.lueluesay.top:chenxi/sub2api.git" did not exist on "aa6f2533745266c6d1bd907ea2c56a889bbd2ca4"
Commit
d9b15879
authored
Jan 04, 2026
by
shaw
Browse files
feat(gateway): 实现 Claude Code 系统提示词智能注入
parent
631ba25e
Changes
1
Show whitespace changes
Inline
Side-by-side
backend/internal/service/gateway_service.go
View file @
d9b15879
...
@@ -30,6 +30,7 @@ const (
...
@@ -30,6 +30,7 @@ const (
claudeAPIURL
=
"https://api.anthropic.com/v1/messages?beta=true"
claudeAPIURL
=
"https://api.anthropic.com/v1/messages?beta=true"
claudeAPICountTokensURL
=
"https://api.anthropic.com/v1/messages/count_tokens?beta=true"
claudeAPICountTokensURL
=
"https://api.anthropic.com/v1/messages/count_tokens?beta=true"
stickySessionTTL
=
time
.
Hour
// 粘性会话TTL
stickySessionTTL
=
time
.
Hour
// 粘性会话TTL
claudeCodeSystemPrompt
=
"You are Claude Code, Anthropic's official CLI for Claude."
)
)
// sseDataRe matches SSE data lines with optional whitespace after colon.
// sseDataRe matches SSE data lines with optional whitespace after colon.
...
@@ -37,6 +38,7 @@ const (
...
@@ -37,6 +38,7 @@ const (
var
(
var
(
sseDataRe
=
regexp
.
MustCompile
(
`^data:\s*`
)
sseDataRe
=
regexp
.
MustCompile
(
`^data:\s*`
)
sessionIDRegex
=
regexp
.
MustCompile
(
`session_([a-f0-9-]{36})`
)
sessionIDRegex
=
regexp
.
MustCompile
(
`session_([a-f0-9-]{36})`
)
claudeCliUserAgentRe
=
regexp
.
MustCompile
(
`^claude-cli/\d+\.\d+\.\d+`
)
)
)
// allowedHeaders 白名单headers(参考CRS项目)
// allowedHeaders 白名单headers(参考CRS项目)
...
@@ -951,6 +953,76 @@ func (s *GatewayService) shouldFailoverUpstreamError(statusCode int) bool {
...
@@ -951,6 +953,76 @@ func (s *GatewayService) shouldFailoverUpstreamError(statusCode int) bool {
}
}
}
}
// isClaudeCodeClient 判断请求是否来自 Claude Code 客户端
// 简化判断:User-Agent 匹配 + metadata.user_id 存在
func
isClaudeCodeClient
(
userAgent
string
,
metadataUserID
string
)
bool
{
if
metadataUserID
==
""
{
return
false
}
return
claudeCliUserAgentRe
.
MatchString
(
userAgent
)
}
// systemIncludesClaudeCodePrompt 检查 system 中是否已包含 Claude Code 提示词
// 支持 string 和 []any 两种格式
func
systemIncludesClaudeCodePrompt
(
system
any
)
bool
{
switch
v
:=
system
.
(
type
)
{
case
string
:
return
v
==
claudeCodeSystemPrompt
case
[]
any
:
for
_
,
item
:=
range
v
{
if
m
,
ok
:=
item
.
(
map
[
string
]
any
);
ok
{
if
text
,
ok
:=
m
[
"text"
]
.
(
string
);
ok
&&
text
==
claudeCodeSystemPrompt
{
return
true
}
}
}
}
return
false
}
// injectClaudeCodePrompt 在 system 开头注入 Claude Code 提示词
// 处理 null、字符串、数组三种格式
func
injectClaudeCodePrompt
(
body
[]
byte
,
system
any
)
[]
byte
{
claudeCodeBlock
:=
map
[
string
]
any
{
"type"
:
"text"
,
"text"
:
claudeCodeSystemPrompt
,
"cache_control"
:
map
[
string
]
string
{
"type"
:
"ephemeral"
},
}
var
newSystem
[]
any
switch
v
:=
system
.
(
type
)
{
case
nil
:
newSystem
=
[]
any
{
claudeCodeBlock
}
case
string
:
if
v
==
""
||
v
==
claudeCodeSystemPrompt
{
newSystem
=
[]
any
{
claudeCodeBlock
}
}
else
{
newSystem
=
[]
any
{
claudeCodeBlock
,
map
[
string
]
any
{
"type"
:
"text"
,
"text"
:
v
}}
}
case
[]
any
:
newSystem
=
make
([]
any
,
0
,
len
(
v
)
+
1
)
newSystem
=
append
(
newSystem
,
claudeCodeBlock
)
for
_
,
item
:=
range
v
{
if
m
,
ok
:=
item
.
(
map
[
string
]
any
);
ok
{
if
text
,
ok
:=
m
[
"text"
]
.
(
string
);
ok
&&
text
==
claudeCodeSystemPrompt
{
continue
}
}
newSystem
=
append
(
newSystem
,
item
)
}
default
:
newSystem
=
[]
any
{
claudeCodeBlock
}
}
result
,
err
:=
sjson
.
SetBytes
(
body
,
"system"
,
newSystem
)
if
err
!=
nil
{
log
.
Printf
(
"Warning: failed to inject Claude Code prompt: %v"
,
err
)
return
body
}
return
result
}
// Forward 转发请求到Claude API
// Forward 转发请求到Claude API
func
(
s
*
GatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
parsed
*
ParsedRequest
)
(
*
ForwardResult
,
error
)
{
func
(
s
*
GatewayService
)
Forward
(
ctx
context
.
Context
,
c
*
gin
.
Context
,
account
*
Account
,
parsed
*
ParsedRequest
)
(
*
ForwardResult
,
error
)
{
startTime
:=
time
.
Now
()
startTime
:=
time
.
Now
()
...
@@ -962,16 +1034,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
...
@@ -962,16 +1034,13 @@ func (s *GatewayService) Forward(ctx context.Context, c *gin.Context, account *A
reqModel
:=
parsed
.
Model
reqModel
:=
parsed
.
Model
reqStream
:=
parsed
.
Stream
reqStream
:=
parsed
.
Stream
if
!
parsed
.
HasSystem
{
// 智能注入 Claude Code 系统提示词(仅 OAuth/SetupToken 账号需要)
body
,
_
=
sjson
.
SetBytes
(
body
,
"system"
,
[]
any
{
// 条件:1) OAuth/SetupToken 账号 2) 不是 Claude Code 客户端 3) 不是 Haiku 模型 4) system 中还没有 Claude Code 提示词
map
[
string
]
any
{
if
account
.
IsOAuth
()
&&
"type"
:
"text"
,
!
isClaudeCodeClient
(
c
.
GetHeader
(
"User-Agent"
),
parsed
.
MetadataUserID
)
&&
"text"
:
"You are Claude Code, Anthropic's official CLI for Claude."
,
!
strings
.
Contains
(
strings
.
ToLower
(
reqModel
),
"haiku"
)
&&
"cache_control"
:
map
[
string
]
string
{
!
systemIncludesClaudeCodePrompt
(
parsed
.
System
)
{
"type"
:
"ephemeral"
,
body
=
injectClaudeCodePrompt
(
body
,
parsed
.
System
)
},
},
})
}
}
// 应用模型映射(仅对apikey类型账号)
// 应用模型映射(仅对apikey类型账号)
...
...
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