Skip to content

Commit 9bf93dd

Browse files
feat: byok-observability for aibridge
1 parent 17c7e03 commit 9bf93dd

File tree

11 files changed

+72
-0
lines changed

11 files changed

+72
-0
lines changed

bridge.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ func newInterceptionProcessor(p provider.Provider, cbs *circuitbreaker.ProviderC
203203
Client: string(client),
204204
ClientSessionID: sessionID,
205205
CorrelatingToolCallID: interceptor.CorrelatingToolCallID(),
206+
CredentialKind: interceptor.CredentialKind(),
207+
CredentialHint: interceptor.CredentialHint(),
206208
}); err != nil {
207209
span.SetStatus(codes.Error, fmt.Sprintf("failed to record interception: %v", err))
208210
logger.Warn(ctx, "failed to record interception", slog.Error(err))

intercept/chatcompletions/base.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ type interceptionBase struct {
3939

4040
recorder recorder.Recorder
4141
mcpProxy mcp.ServerProxier
42+
43+
intercept.CredentialFields
4244
}
4345

4446
func (i *interceptionBase) newCompletionsService() openai.ChatCompletionService {

intercept/credential.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package intercept
2+
3+
// Credential kind constants for interception recording.
4+
const (
5+
CredentialKindCentralized = "centralized"
6+
CredentialKindPersonalAPIKey = "personal_api_key"
7+
CredentialKindSubscription = "subscription"
8+
)
9+
10+
// MaskCredential returns a masked version of a credential suitable for
11+
// audit logging. It preserves a short prefix and suffix separated by
12+
// an ellipsis. Returns an empty string for empty input.
13+
func MaskCredential(secret string) string {
14+
if len(secret) <= 8 {
15+
return "***"
16+
}
17+
return secret[:4] + "..." + secret[len(secret)-4:]
18+
}
19+
20+
// CredentialFields is an embeddable helper that implements the
21+
// CredentialKind, CredentialHint, and SetCredential methods of the
22+
// Interceptor interface.
23+
type CredentialFields struct {
24+
Kind string
25+
Hint string
26+
}
27+
28+
func (c *CredentialFields) CredentialKind() string {
29+
return c.Kind
30+
}
31+
32+
func (c *CredentialFields) CredentialHint() string {
33+
return c.Hint
34+
}
35+
36+
func (c *CredentialFields) SetCredential(kind, hint string) {
37+
c.Kind = kind
38+
c.Hint = hint
39+
}

intercept/interceptor.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ type Interceptor interface {
2525
Streaming() bool
2626
// TraceAttributes returns tracing attributes for this [Interceptor]
2727
TraceAttributes(*http.Request) []attribute.KeyValue
28+
// SetCredential sets the credential kind and hint for this
29+
// interception. Called by the provider after construction.
30+
SetCredential(kind, hint string)
31+
// CredentialKind returns how the request was authenticated:
32+
// centralized, personal_api_key, or subscription.
33+
CredentialKind() string
34+
// CredentialHint returns a masked credential identifier for audit
35+
// purposes (e.g. sk-...abc1).
36+
CredentialHint() string
2837
// CorrelatingToolCallID returns the ID of a tool call result submitted
2938
// in the request, if present. This is used to correlate the current
3039
// interception back to the previous interception that issued those tool

intercept/messages/base.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,8 @@ type interceptionBase struct {
7878

7979
recorder recorder.Recorder
8080
mcpProxy mcp.ServerProxier
81+
82+
intercept.CredentialFields
8183
}
8284

8385
func (i *interceptionBase) ID() uuid.UUID {

intercept/responses/base.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ type responsesInterceptionBase struct {
4848

4949
logger slog.Logger
5050
tracer trace.Tracer
51+
52+
intercept.CredentialFields
5153
}
5254

5355
func (i *responsesInterceptionBase) newResponsesService() responses.ResponseService {

provider/anthropic.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,14 +123,17 @@ func (p *Anthropic) CreateInterceptor(w http.ResponseWriter, r *http.Request, tr
123123
// set BYOKBearerToken and clear the centralized key.
124124
// When both are present, X-Api-Key takes priority to match
125125
// claude-code behavior.
126+
credKind := intercept.CredentialKindCentralized
126127
authHeaderName := p.AuthHeader()
127128
if apiKey := r.Header.Get("X-Api-Key"); apiKey != "" {
128129
cfg.Key = apiKey
129130
authHeaderName = "X-Api-Key"
131+
credKind = intercept.CredentialKindPersonalAPIKey
130132
} else if token := utils.ExtractBearerToken(r.Header.Get("Authorization")); token != "" {
131133
cfg.BYOKBearerToken = token
132134
cfg.Key = ""
133135
authHeaderName = "Authorization"
136+
credKind = intercept.CredentialKindPersonalAPIKey
134137
}
135138

136139
var interceptor intercept.Interceptor
@@ -139,6 +142,12 @@ func (p *Anthropic) CreateInterceptor(w http.ResponseWriter, r *http.Request, tr
139142
} else {
140143
interceptor = messages.NewBlockingInterceptor(id, reqPayload, cfg, p.bedrockCfg, r.Header, authHeaderName, tracer)
141144
}
145+
// Determine the credential hint from the key actually used.
146+
credHint := intercept.MaskCredential(cfg.Key)
147+
if cfg.BYOKBearerToken != "" {
148+
credHint = intercept.MaskCredential(cfg.BYOKBearerToken)
149+
}
150+
interceptor.SetCredential(credKind, credHint)
142151
span.SetAttributes(interceptor.TraceAttributes(r)...)
143152
return interceptor, nil
144153
}

provider/chatgpt.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ func (p *ChatGPT) CreateInterceptor(w http.ResponseWriter, r *http.Request, trac
145145
span.SetStatus(codes.Error, "unknown route: "+r.URL.Path)
146146
return nil, UnknownRoute
147147
}
148+
interceptor.SetCredential(intercept.CredentialKindSubscription, intercept.MaskCredential(token))
148149
span.SetAttributes(interceptor.TraceAttributes(r)...)
149150
return interceptor, nil
150151
}

provider/copilot.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ func (p *Copilot) CreateInterceptor(_ http.ResponseWriter, r *http.Request, trac
175175
return nil, UnknownRoute
176176
}
177177

178+
interceptor.SetCredential(intercept.CredentialKindSubscription, intercept.MaskCredential(key))
178179
span.SetAttributes(interceptor.TraceAttributes(r)...)
179180
return interceptor, nil
180181
}

provider/openai.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,10 @@ func (p *OpenAI) CreateInterceptor(w http.ResponseWriter, r *http.Request, trace
106106
//
107107
// In BYOK mode the user's credential is in Authorization. Replace
108108
// the centralized key with it so it is forwarded upstream.
109+
credKind := intercept.CredentialKindCentralized
109110
if token := utils.ExtractBearerToken(r.Header.Get("Authorization")); token != "" {
110111
cfg.Key = token
112+
credKind = intercept.CredentialKindPersonalAPIKey
111113
}
112114

113115
path := strings.TrimPrefix(r.URL.Path, p.RoutePrefix())
@@ -143,6 +145,7 @@ func (p *OpenAI) CreateInterceptor(w http.ResponseWriter, r *http.Request, trace
143145
span.SetStatus(codes.Error, "unknown route: "+r.URL.Path)
144146
return nil, UnknownRoute
145147
}
148+
interceptor.SetCredential(credKind, intercept.MaskCredential(cfg.Key))
146149
span.SetAttributes(interceptor.TraceAttributes(r)...)
147150
return interceptor, nil
148151
}

0 commit comments

Comments
 (0)