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
a6764e82
Commit
a6764e82
authored
Mar 19, 2026
by
shaw
Browse files
修复 OAuth/SetupToken 转发请求体重排并增加调试开关
parent
9f6ab6b8
Changes
8
Expand all
Hide whitespace changes
Inline
Side-by-side
backend/internal/pkg/antigravity/request_transformer.go
View file @
a6764e82
...
...
@@ -275,21 +275,6 @@ func filterOpenCodePrompt(text string) string {
return
""
}
// systemBlockFilterPrefixes 需要从 system 中过滤的文本前缀列表
var
systemBlockFilterPrefixes
=
[]
string
{
"x-anthropic-billing-header"
,
}
// filterSystemBlockByPrefix 如果文本匹配过滤前缀,返回空字符串
func
filterSystemBlockByPrefix
(
text
string
)
string
{
for
_
,
prefix
:=
range
systemBlockFilterPrefixes
{
if
strings
.
HasPrefix
(
text
,
prefix
)
{
return
""
}
}
return
text
}
// buildSystemInstruction 构建 systemInstruction(与 Antigravity-Manager 保持一致)
func
buildSystemInstruction
(
system
json
.
RawMessage
,
modelName
string
,
opts
TransformOptions
,
tools
[]
ClaudeTool
)
*
GeminiContent
{
var
parts
[]
GeminiPart
...
...
@@ -306,8 +291,8 @@ func buildSystemInstruction(system json.RawMessage, modelName string, opts Trans
if
strings
.
Contains
(
sysStr
,
"You are Antigravity"
)
{
userHasAntigravityIdentity
=
true
}
// 过滤 OpenCode 默认提示词
和黑名单前缀
filtered
:=
filterSystemBlockByPrefix
(
filterOpenCodePrompt
(
sysStr
)
)
// 过滤 OpenCode 默认提示词
filtered
:=
filterOpenCodePrompt
(
sysStr
)
if
filtered
!=
""
{
userSystemParts
=
append
(
userSystemParts
,
GeminiPart
{
Text
:
filtered
})
}
...
...
@@ -321,8 +306,8 @@ func buildSystemInstruction(system json.RawMessage, modelName string, opts Trans
if
strings
.
Contains
(
block
.
Text
,
"You are Antigravity"
)
{
userHasAntigravityIdentity
=
true
}
// 过滤 OpenCode 默认提示词
和黑名单前缀
filtered
:=
filterSystemBlockByPrefix
(
filterOpenCodePrompt
(
block
.
Text
)
)
// 过滤 OpenCode 默认提示词
filtered
:=
filterOpenCodePrompt
(
block
.
Text
)
if
filtered
!=
""
{
userSystemParts
=
append
(
userSystemParts
,
GeminiPart
{
Text
:
filtered
})
}
...
...
backend/internal/pkg/antigravity/request_transformer_test.go
View file @
a6764e82
...
...
@@ -2,7 +2,10 @@ package antigravity
import
(
"encoding/json"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
// TestBuildParts_ThinkingBlockWithoutSignature 测试thinking block无signature时的处理
...
...
@@ -349,3 +352,51 @@ func TestBuildGenerationConfig_ThinkingDynamicBudget(t *testing.T) {
})
}
}
func
TestTransformClaudeToGeminiWithOptions_PreservesBillingHeaderSystemBlock
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
system
json
.
RawMessage
}{
{
name
:
"system array"
,
system
:
json
.
RawMessage
(
`[{"type":"text","text":"x-anthropic-billing-header keep"}]`
),
},
{
name
:
"system string"
,
system
:
json
.
RawMessage
(
`"x-anthropic-billing-header keep"`
),
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
claudeReq
:=
&
ClaudeRequest
{
Model
:
"claude-3-5-sonnet-latest"
,
System
:
tt
.
system
,
Messages
:
[]
ClaudeMessage
{
{
Role
:
"user"
,
Content
:
json
.
RawMessage
(
`[{"type":"text","text":"hello"}]`
),
},
},
}
body
,
err
:=
TransformClaudeToGeminiWithOptions
(
claudeReq
,
"project-1"
,
"gemini-2.5-flash"
,
DefaultTransformOptions
())
require
.
NoError
(
t
,
err
)
var
req
V1InternalRequest
require
.
NoError
(
t
,
json
.
Unmarshal
(
body
,
&
req
))
require
.
NotNil
(
t
,
req
.
Request
.
SystemInstruction
)
found
:=
false
for
_
,
part
:=
range
req
.
Request
.
SystemInstruction
.
Parts
{
if
strings
.
Contains
(
part
.
Text
,
"x-anthropic-billing-header keep"
)
{
found
=
true
break
}
}
require
.
True
(
t
,
found
,
"转换后的 systemInstruction 应保留 x-anthropic-billing-header 内容"
)
})
}
}
backend/internal/service/gateway_anthropic_apikey_passthrough_test.go
View file @
a6764e82
...
...
@@ -688,6 +688,83 @@ func TestGatewayService_AnthropicOAuth_NotAffectedByAPIKeyPassthroughToggle(t *t
require
.
Contains
(
t
,
req
.
Header
.
Get
(
"anthropic-beta"
),
claude
.
BetaOAuth
,
"OAuth 链路仍应按原逻辑补齐 oauth beta"
)
}
func
TestGatewayService_AnthropicOAuth_ForwardPreservesBillingHeaderSystemBlock
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
tests
:=
[]
struct
{
name
string
body
string
}{
{
name
:
"system array"
,
body
:
`{"model":"claude-3-5-sonnet-latest","system":[{"type":"text","text":"x-anthropic-billing-header keep"}],"messages":[{"role":"user","content":[{"type":"text","text":"hello"}]}]}`
,
},
{
name
:
"system string"
,
body
:
`{"model":"claude-3-5-sonnet-latest","system":"x-anthropic-billing-header keep","messages":[{"role":"user","content":[{"type":"text","text":"hello"}]}]}`
,
},
}
for
_
,
tt
:=
range
tests
{
t
.
Run
(
tt
.
name
,
func
(
t
*
testing
.
T
)
{
rec
:=
httptest
.
NewRecorder
()
c
,
_
:=
gin
.
CreateTestContext
(
rec
)
c
.
Request
=
httptest
.
NewRequest
(
http
.
MethodPost
,
"/v1/messages"
,
nil
)
parsed
,
err
:=
ParseGatewayRequest
([]
byte
(
tt
.
body
),
PlatformAnthropic
)
require
.
NoError
(
t
,
err
)
upstream
:=
&
anthropicHTTPUpstreamRecorder
{
resp
:
&
http
.
Response
{
StatusCode
:
http
.
StatusOK
,
Header
:
http
.
Header
{
"Content-Type"
:
[]
string
{
"application/json"
},
"x-request-id"
:
[]
string
{
"rid-oauth-preserve"
},
},
Body
:
io
.
NopCloser
(
strings
.
NewReader
(
`{"id":"msg_1","type":"message","role":"assistant","model":"claude-3-5-sonnet-20241022","content":[{"type":"text","text":"ok"}],"usage":{"input_tokens":12,"output_tokens":7}}`
)),
},
}
cfg
:=
&
config
.
Config
{
Gateway
:
config
.
GatewayConfig
{
MaxLineSize
:
defaultMaxLineSize
,
},
}
svc
:=
&
GatewayService
{
cfg
:
cfg
,
responseHeaderFilter
:
compileResponseHeaderFilter
(
cfg
),
httpUpstream
:
upstream
,
rateLimitService
:
&
RateLimitService
{},
deferredService
:
&
DeferredService
{},
}
account
:=
&
Account
{
ID
:
301
,
Name
:
"anthropic-oauth-preserve"
,
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeOAuth
,
Concurrency
:
1
,
Credentials
:
map
[
string
]
any
{
"access_token"
:
"oauth-token"
,
},
Status
:
StatusActive
,
Schedulable
:
true
,
}
result
,
err
:=
svc
.
Forward
(
context
.
Background
(),
c
,
account
,
parsed
)
require
.
NoError
(
t
,
err
)
require
.
NotNil
(
t
,
result
)
require
.
NotNil
(
t
,
upstream
.
lastReq
)
require
.
Equal
(
t
,
"Bearer oauth-token"
,
upstream
.
lastReq
.
Header
.
Get
(
"authorization"
))
require
.
Contains
(
t
,
upstream
.
lastReq
.
Header
.
Get
(
"anthropic-beta"
),
claude
.
BetaOAuth
)
system
:=
gjson
.
GetBytes
(
upstream
.
lastBody
,
"system"
)
require
.
True
(
t
,
system
.
Exists
())
require
.
Contains
(
t
,
system
.
Raw
,
"x-anthropic-billing-header keep"
)
})
}
}
func
TestGatewayService_AnthropicAPIKeyPassthrough_StreamingStillCollectsUsageAfterClientDisconnect
(
t
*
testing
.
T
)
{
gin
.
SetMode
(
gin
.
TestMode
)
...
...
backend/internal/service/gateway_body_order_test.go
0 → 100644
View file @
a6764e82
package
service
import
(
"strings"
"testing"
"github.com/Wei-Shaw/sub2api/internal/pkg/claude"
"github.com/stretchr/testify/require"
)
func
assertJSONTokenOrder
(
t
*
testing
.
T
,
body
string
,
tokens
...
string
)
{
t
.
Helper
()
last
:=
-
1
for
_
,
token
:=
range
tokens
{
pos
:=
strings
.
Index
(
body
,
token
)
require
.
NotEqualf
(
t
,
-
1
,
pos
,
"missing token %s in body %s"
,
token
,
body
)
require
.
Greaterf
(
t
,
pos
,
last
,
"token %s should appear after previous tokens in body %s"
,
token
,
body
)
last
=
pos
}
}
func
TestReplaceModelInBody_PreservesTopLevelFieldOrder
(
t
*
testing
.
T
)
{
svc
:=
&
GatewayService
{}
body
:=
[]
byte
(
`{"alpha":1,"model":"claude-3-5-sonnet-latest","messages":[],"omega":2}`
)
result
:=
svc
.
replaceModelInBody
(
body
,
"claude-3-5-sonnet-20241022"
)
resultStr
:=
string
(
result
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"model"`
,
`"messages"`
,
`"omega"`
)
require
.
Contains
(
t
,
resultStr
,
`"model":"claude-3-5-sonnet-20241022"`
)
}
func
TestNormalizeClaudeOAuthRequestBody_PreservesTopLevelFieldOrder
(
t
*
testing
.
T
)
{
body
:=
[]
byte
(
`{"alpha":1,"model":"claude-3-5-sonnet-latest","temperature":0.2,"system":"You are OpenCode, the best coding agent on the planet.","messages":[],"tool_choice":{"type":"auto"},"omega":2}`
)
result
,
modelID
:=
normalizeClaudeOAuthRequestBody
(
body
,
"claude-3-5-sonnet-latest"
,
claudeOAuthNormalizeOptions
{
injectMetadata
:
true
,
metadataUserID
:
"user-1"
,
})
resultStr
:=
string
(
result
)
require
.
Equal
(
t
,
claude
.
NormalizeModelID
(
"claude-3-5-sonnet-latest"
),
modelID
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"model"`
,
`"system"`
,
`"messages"`
,
`"omega"`
,
`"tools"`
,
`"metadata"`
)
require
.
NotContains
(
t
,
resultStr
,
`"temperature"`
)
require
.
NotContains
(
t
,
resultStr
,
`"tool_choice"`
)
require
.
Contains
(
t
,
resultStr
,
`"system":"`
+
claudeCodeSystemPrompt
+
`"`
)
require
.
Contains
(
t
,
resultStr
,
`"tools":[]`
)
require
.
Contains
(
t
,
resultStr
,
`"metadata":{"user_id":"user-1"}`
)
}
func
TestInjectClaudeCodePrompt_PreservesFieldOrder
(
t
*
testing
.
T
)
{
body
:=
[]
byte
(
`{"alpha":1,"system":[{"id":"block-1","type":"text","text":"Custom"}],"messages":[],"omega":2}`
)
result
:=
injectClaudeCodePrompt
(
body
,
[]
any
{
map
[
string
]
any
{
"id"
:
"block-1"
,
"type"
:
"text"
,
"text"
:
"Custom"
},
})
resultStr
:=
string
(
result
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"system"`
,
`"messages"`
,
`"omega"`
)
require
.
Contains
(
t
,
resultStr
,
`{"id":"block-1","type":"text","text":"`
+
claudeCodeSystemPrompt
+
`\n\nCustom"}`
)
}
func
TestEnforceCacheControlLimit_PreservesTopLevelFieldOrder
(
t
*
testing
.
T
)
{
body
:=
[]
byte
(
`{"alpha":1,"system":[{"type":"text","text":"s1","cache_control":{"type":"ephemeral"}},{"type":"text","text":"s2","cache_control":{"type":"ephemeral"}}],"messages":[{"role":"user","content":[{"type":"text","text":"m1","cache_control":{"type":"ephemeral"}},{"type":"text","text":"m2","cache_control":{"type":"ephemeral"}},{"type":"text","text":"m3","cache_control":{"type":"ephemeral"}}]}],"omega":2}`
)
result
:=
enforceCacheControlLimit
(
body
)
resultStr
:=
string
(
result
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"system"`
,
`"messages"`
,
`"omega"`
)
require
.
Equal
(
t
,
4
,
strings
.
Count
(
resultStr
,
`"cache_control"`
))
}
backend/internal/service/gateway_debug_env_test.go
0 → 100644
View file @
a6764e82
package
service
import
"testing"
func
TestDebugGatewayBodyLoggingEnabled
(
t
*
testing
.
T
)
{
t
.
Run
(
"default disabled"
,
func
(
t
*
testing
.
T
)
{
t
.
Setenv
(
debugGatewayBodyEnv
,
""
)
if
debugGatewayBodyLoggingEnabled
()
{
t
.
Fatalf
(
"expected debug gateway body logging to be disabled by default"
)
}
})
t
.
Run
(
"enabled with true-like values"
,
func
(
t
*
testing
.
T
)
{
for
_
,
value
:=
range
[]
string
{
"1"
,
"true"
,
"TRUE"
,
"yes"
,
"on"
}
{
t
.
Run
(
value
,
func
(
t
*
testing
.
T
)
{
t
.
Setenv
(
debugGatewayBodyEnv
,
value
)
if
!
debugGatewayBodyLoggingEnabled
()
{
t
.
Fatalf
(
"expected debug gateway body logging to be enabled for %q"
,
value
)
}
})
}
})
t
.
Run
(
"disabled with other values"
,
func
(
t
*
testing
.
T
)
{
for
_
,
value
:=
range
[]
string
{
"0"
,
"false"
,
"off"
,
"debug"
}
{
t
.
Run
(
value
,
func
(
t
*
testing
.
T
)
{
t
.
Setenv
(
debugGatewayBodyEnv
,
value
)
if
debugGatewayBodyLoggingEnabled
()
{
t
.
Fatalf
(
"expected debug gateway body logging to be disabled for %q"
,
value
)
}
})
}
})
}
backend/internal/service/gateway_service.go
View file @
a6764e82
This diff is collapsed.
Click to expand it.
backend/internal/service/identity_service.go
View file @
a6764e82
...
...
@@ -5,7 +5,6 @@ import (
"crypto/rand"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"log/slog"
"net/http"
...
...
@@ -15,6 +14,8 @@ import (
"time"
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/tidwall/gjson"
"github.com/tidwall/sjson"
)
// 预编译正则表达式(避免每次调用重新编译)
...
...
@@ -215,25 +216,20 @@ func (s *IdentityService) RewriteUserID(body []byte, accountID int64, accountUUI
return
body
,
nil
}
// 使用 RawMessage 保留其他字段的原始字节
var
reqMap
map
[
string
]
json
.
RawMessage
if
err
:=
json
.
Unmarshal
(
body
,
&
reqMap
);
err
!=
nil
{
metadata
:=
gjson
.
GetBytes
(
body
,
"metadata"
)
if
!
metadata
.
Exists
()
||
metadata
.
Type
==
gjson
.
Null
{
return
body
,
nil
}
// 解析 metadata 字段
metadataRaw
,
ok
:=
reqMap
[
"metadata"
]
if
!
ok
{
if
!
strings
.
HasPrefix
(
strings
.
TrimSpace
(
metadata
.
Raw
),
"{"
)
{
return
body
,
nil
}
var
metadata
map
[
string
]
any
if
err
:=
json
.
Unmarshal
(
metadataRaw
,
&
metadata
);
err
!=
nil
{
userIDResult
:=
metadata
.
Get
(
"user_id"
)
if
!
userIDResult
.
Exists
()
||
userIDResult
.
Type
!=
gjson
.
String
{
return
body
,
nil
}
userID
,
ok
:=
metadata
[
"user_id"
]
.
(
string
)
if
!
ok
||
userID
==
""
{
userID
:=
userIDResult
.
String
()
if
userID
==
""
{
return
body
,
nil
}
...
...
@@ -252,17 +248,15 @@ func (s *IdentityService) RewriteUserID(body []byte, accountID int64, accountUUI
// 根据客户端版本选择输出格式
version
:=
ExtractCLIVersion
(
fingerprintUA
)
newUserID
:=
FormatMetadataUserID
(
cachedClientID
,
accountUUID
,
newSessionHash
,
version
)
if
newUserID
==
userID
{
return
body
,
nil
}
metadata
[
"user_id"
]
=
newUserID
// 只重新序列化 metadata 字段
newMetadataRaw
,
err
:=
json
.
Marshal
(
metadata
)
newBody
,
err
:=
sjson
.
SetBytes
(
body
,
"metadata.user_id"
,
newUserID
)
if
err
!=
nil
{
return
body
,
nil
}
reqMap
[
"metadata"
]
=
newMetadataRaw
return
json
.
Marshal
(
reqMap
)
return
newBody
,
nil
}
// RewriteUserIDWithMasking 重写body中的metadata.user_id,支持会话ID伪装
...
...
@@ -283,25 +277,20 @@ func (s *IdentityService) RewriteUserIDWithMasking(ctx context.Context, body []b
return
newBody
,
nil
}
// 使用 RawMessage 保留其他字段的原始字节
var
reqMap
map
[
string
]
json
.
RawMessage
if
err
:=
json
.
Unmarshal
(
newBody
,
&
reqMap
);
err
!=
nil
{
metadata
:=
gjson
.
GetBytes
(
newBody
,
"metadata"
)
if
!
metadata
.
Exists
()
||
metadata
.
Type
==
gjson
.
Null
{
return
newBody
,
nil
}
// 解析 metadata 字段
metadataRaw
,
ok
:=
reqMap
[
"metadata"
]
if
!
ok
{
if
!
strings
.
HasPrefix
(
strings
.
TrimSpace
(
metadata
.
Raw
),
"{"
)
{
return
newBody
,
nil
}
var
metadata
map
[
string
]
any
if
err
:=
json
.
Unmarshal
(
metadataRaw
,
&
metadata
);
err
!=
nil
{
userIDResult
:=
metadata
.
Get
(
"user_id"
)
if
!
userIDResult
.
Exists
()
||
userIDResult
.
Type
!=
gjson
.
String
{
return
newBody
,
nil
}
userID
,
ok
:=
metadata
[
"user_id"
]
.
(
string
)
if
!
ok
||
userID
==
""
{
userID
:=
userIDResult
.
String
()
if
userID
==
""
{
return
newBody
,
nil
}
...
...
@@ -339,16 +328,15 @@ func (s *IdentityService) RewriteUserIDWithMasking(ctx context.Context, body []b
"after"
,
newUserID
,
)
metadata
[
"user_id"
]
=
newUserID
// 只重新序列化 metadata 字段
newMetadataRaw
,
marshalErr
:=
json
.
Marshal
(
metadata
)
if
marshalErr
!=
nil
{
if
newUserID
==
userID
{
return
newBody
,
nil
}
reqMap
[
"metadata"
]
=
newMetadataRaw
return
json
.
Marshal
(
reqMap
)
maskedBody
,
setErr
:=
sjson
.
SetBytes
(
newBody
,
"metadata.user_id"
,
newUserID
)
if
setErr
!=
nil
{
return
newBody
,
nil
}
return
maskedBody
,
nil
}
// generateRandomUUID 生成随机 UUID v4 格式字符串
...
...
backend/internal/service/identity_service_order_test.go
0 → 100644
View file @
a6764e82
package
service
import
(
"context"
"strings"
"testing"
"github.com/stretchr/testify/require"
)
type
identityCacheStub
struct
{
maskedSessionID
string
}
func
(
s
*
identityCacheStub
)
GetFingerprint
(
_
context
.
Context
,
_
int64
)
(
*
Fingerprint
,
error
)
{
return
nil
,
nil
}
func
(
s
*
identityCacheStub
)
SetFingerprint
(
_
context
.
Context
,
_
int64
,
_
*
Fingerprint
)
error
{
return
nil
}
func
(
s
*
identityCacheStub
)
GetMaskedSessionID
(
_
context
.
Context
,
_
int64
)
(
string
,
error
)
{
return
s
.
maskedSessionID
,
nil
}
func
(
s
*
identityCacheStub
)
SetMaskedSessionID
(
_
context
.
Context
,
_
int64
,
sessionID
string
)
error
{
s
.
maskedSessionID
=
sessionID
return
nil
}
func
TestIdentityService_RewriteUserID_PreservesTopLevelFieldOrder
(
t
*
testing
.
T
)
{
cache
:=
&
identityCacheStub
{}
svc
:=
NewIdentityService
(
cache
)
originalUserID
:=
FormatMetadataUserID
(
"d61f76d0730d2b920763648949bad5c79742155c27037fc77ac3f9805cb90169"
,
""
,
"7578cf37-aaca-46e4-a45c-71285d9dbb83"
,
"2.1.78"
,
)
body
:=
[]
byte
(
`{"alpha":1,"messages":[],"metadata":{"user_id":`
+
strconvQuote
(
originalUserID
)
+
`},"max_tokens":64000,"thinking":{"type":"adaptive"},"output_config":{"effort":"high"},"stream":true}`
)
result
,
err
:=
svc
.
RewriteUserID
(
body
,
123
,
"acc-uuid"
,
"client-xyz"
,
"claude-cli/2.1.78 (external, cli)"
)
require
.
NoError
(
t
,
err
)
resultStr
:=
string
(
result
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"messages"`
,
`"metadata"`
,
`"max_tokens"`
,
`"thinking"`
,
`"output_config"`
,
`"stream"`
)
require
.
NotContains
(
t
,
resultStr
,
originalUserID
)
require
.
Contains
(
t
,
resultStr
,
`"metadata":{"user_id":"`
)
}
func
TestIdentityService_RewriteUserIDWithMasking_PreservesTopLevelFieldOrder
(
t
*
testing
.
T
)
{
cache
:=
&
identityCacheStub
{
maskedSessionID
:
"11111111-2222-4333-8444-555555555555"
}
svc
:=
NewIdentityService
(
cache
)
originalUserID
:=
FormatMetadataUserID
(
"d61f76d0730d2b920763648949bad5c79742155c27037fc77ac3f9805cb90169"
,
""
,
"7578cf37-aaca-46e4-a45c-71285d9dbb83"
,
"2.1.78"
,
)
body
:=
[]
byte
(
`{"alpha":1,"messages":[],"metadata":{"user_id":`
+
strconvQuote
(
originalUserID
)
+
`},"max_tokens":64000,"thinking":{"type":"adaptive"},"output_config":{"effort":"high"},"stream":true}`
)
account
:=
&
Account
{
ID
:
123
,
Platform
:
PlatformAnthropic
,
Type
:
AccountTypeOAuth
,
Extra
:
map
[
string
]
any
{
"session_id_masking_enabled"
:
true
,
},
}
result
,
err
:=
svc
.
RewriteUserIDWithMasking
(
context
.
Background
(),
body
,
account
,
"acc-uuid"
,
"client-xyz"
,
"claude-cli/2.1.78 (external, cli)"
)
require
.
NoError
(
t
,
err
)
resultStr
:=
string
(
result
)
assertJSONTokenOrder
(
t
,
resultStr
,
`"alpha"`
,
`"messages"`
,
`"metadata"`
,
`"max_tokens"`
,
`"thinking"`
,
`"output_config"`
,
`"stream"`
)
require
.
Contains
(
t
,
resultStr
,
cache
.
maskedSessionID
)
require
.
True
(
t
,
strings
.
Contains
(
resultStr
,
`"metadata":{"user_id":"`
))
}
func
strconvQuote
(
v
string
)
string
{
return
`"`
+
strings
.
ReplaceAll
(
strings
.
ReplaceAll
(
v
,
`\`
,
`\\`
),
`"`
,
`\"`
)
+
`"`
}
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