Skip to content

Commit 09967c0

Browse files
committed
feat(openrouter): send app attribution headers and session_id for sticky routing
Add HTTP-Referer and X-Title headers to OpenRouter requests so usage is attributed to Crush on the OpenRouter dashboard. Inject session_id into the request body via ProviderOptions.ExtraBody for sticky routing and downstream provider session grouping. Resolves #3467
1 parent f69170a commit 09967c0

1 file changed

Lines changed: 41 additions & 2 deletions

File tree

internal/agent/coordinator.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,11 @@ func (c *coordinator) run(ctx context.Context, accept *AcceptedRun, sessionID st
252252

253253
mergedOptions, temp, topP, topK, freqPenalty, presPenalty := mergeCallOptions(model, providerCfg)
254254

255+
// Inject session_id for OpenRouter sticky routing and upstream session grouping.
256+
if providerCfg.Type == openrouter.Name {
257+
injectOpenrouterSessionID(mergedOptions, sessionID)
258+
}
259+
255260
if err := c.refreshTokenIfExpired(ctx, providerCfg); err != nil {
256261
// NOTE(@andreynering): We don't return here because the event handling to ask the user to reauthenticate
257262
// depends on the flow below. If refresh fails, proceed with the token we have.
@@ -600,6 +605,19 @@ func getProviderOptions(model Model, providerCfg config.ProviderConfig) fantasy.
600605
return options
601606
}
602607

608+
// injectOpenrouterSessionID adds session_id to OpenRouter ProviderOptions
609+
// ExtraBody for sticky routing and upstream session grouping.
610+
func injectOpenrouterSessionID(opts fantasy.ProviderOptions, sessionID string) {
611+
if o, ok := opts[openrouter.Name].(*openrouter.ProviderOptions); ok {
612+
if o.ExtraBody == nil {
613+
o.ExtraBody = make(map[string]any)
614+
}
615+
if _, exists := o.ExtraBody["session_id"]; !exists {
616+
o.ExtraBody["session_id"] = session.HashID(sessionID)
617+
}
618+
}
619+
}
620+
603621
func mergeCallOptions(model Model, cfg config.ProviderConfig) (fantasy.ProviderOptions, *float64, *float64, *int64, *float64, *float64) {
604622
modelOptions := getProviderOptions(model, cfg)
605623
temp := cmp.Or(model.ModelCfg.Temperature, model.CatwalkCfg.Options.Temperature)
@@ -941,6 +959,15 @@ func (c *coordinator) buildOpenaiProvider(baseURL, apiKey string, headers map[st
941959
}
942960

943961
func (c *coordinator) buildOpenrouterProvider(_, apiKey string, headers map[string]string) (fantasy.Provider, error) {
962+
if headers == nil {
963+
headers = make(map[string]string)
964+
}
965+
if _, ok := headers["HTTP-Referer"]; !ok {
966+
headers["HTTP-Referer"] = "https://charm.land/crush"
967+
}
968+
if _, ok := headers["X-Title"]; !ok {
969+
headers["X-Title"] = "Crush"
970+
}
944971
opts := []openrouter.Option{
945972
openrouter.WithAPIKey(apiKey),
946973
}
@@ -1246,9 +1273,15 @@ func (c *coordinator) Summarize(ctx context.Context, sessionID string) error {
12461273
slog.Error("Failed to refresh OAuth2 token before summarize. Proceeding with existing token.", "error", err)
12471274
}
12481275

1276+
// Inject session_id for OpenRouter sticky routing.
1277+
opts := getProviderOptions(c.currentAgent.Model(), providerCfg)
1278+
if providerCfg.Type == openrouter.Name {
1279+
injectOpenrouterSessionID(opts, sessionID)
1280+
}
1281+
12491282
// Auth failures during summarize flow through fantasy's OnAuthRefresh,
12501283
// the same path used by regular turns.
1251-
return c.currentAgent.Summarize(ctx, sessionID, getProviderOptions(c.currentAgent.Model(), providerCfg), c.makeAuthRefreshCallback(providerCfg))
1284+
return c.currentAgent.Summarize(ctx, sessionID, opts, c.makeAuthRefreshCallback(providerCfg))
12521285
}
12531286

12541287
// GenerateTitle generates a session title using the current agent.
@@ -1427,13 +1460,19 @@ func (c *coordinator) runSubAgent(ctx context.Context, params subAgentParams) (f
14271460
return fantasy.ToolResponse{}, errModelProviderNotConfigured
14281461
}
14291462

1463+
// Inject session_id for OpenRouter sticky routing.
1464+
subOpts := getProviderOptions(model, providerCfg)
1465+
if providerCfg.Type == openrouter.Name {
1466+
injectOpenrouterSessionID(subOpts, session.ID)
1467+
}
1468+
14301469
// Run the agent
14311470
run := func() (*fantasy.AgentResult, error) {
14321471
return params.Agent.Run(ctx, SessionAgentCall{
14331472
SessionID: session.ID,
14341473
Prompt: params.Prompt,
14351474
MaxOutputTokens: maxTokens,
1436-
ProviderOptions: getProviderOptions(model, providerCfg),
1475+
ProviderOptions: subOpts,
14371476
Temperature: model.ModelCfg.Temperature,
14381477
TopP: model.ModelCfg.TopP,
14391478
TopK: model.ModelCfg.TopK,

0 commit comments

Comments
 (0)