@@ -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+
603621func 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
943961func (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