chore: revert change to skip user-agent for openrouter

Andrey Nering created

Change summary

providers/openai/call_useragent.go           | 10 +-----
providers/openai/language_model.go           | 19 ++---------
providers/openai/openai.go                   | 35 +++------------------
providers/openai/responses_language_model.go | 28 ++++++++---------
providers/openrouter/openrouter.go           |  1 
providers/openrouter/useragent_test.go       |  2 
6 files changed, 25 insertions(+), 70 deletions(-)

Detailed changes

providers/openai/call_useragent.go 🔗

@@ -13,10 +13,7 @@ import (
 // When noDefaultUA is true the SDK's own User-Agent is preserved and no
 // override is applied (needed for providers like OpenRouter, which reject
 // User-Agent headers they don't expect).
-func callUARequestOptions(call fantasy.Call, noDefaultUA bool) []option.RequestOption {
-	if noDefaultUA {
-		return nil
-	}
+func callUARequestOptions(call fantasy.Call) []option.RequestOption {
 	if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
 		return []option.RequestOption{option.WithHeader("User-Agent", ua)}
 	}
@@ -26,10 +23,7 @@ func callUARequestOptions(call fantasy.Call, noDefaultUA bool) []option.RequestO
 // objectCallUARequestOptions returns per-request options that override the
 // client-level User-Agent header when the ObjectCall carries agent-level UA
 // settings.
-func objectCallUARequestOptions(call fantasy.ObjectCall, noDefaultUA bool) []option.RequestOption {
-	if noDefaultUA {
-		return nil
-	}
+func objectCallUARequestOptions(call fantasy.ObjectCall) []option.RequestOption {
 	if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
 		return []option.RequestOption{option.WithHeader("User-Agent", ua)}
 	}

providers/openai/language_model.go 🔗

@@ -32,7 +32,6 @@ type languageModel struct {
 	streamExtraFunc            LanguageModelStreamExtraFunc
 	streamProviderMetadataFunc LanguageModelStreamProviderMetadataFunc
 	toPromptFunc               LanguageModelToPromptFunc
-	noDefaultUserAgent         bool
 }
 
 // LanguageModelOption is a function that configures a languageModel.
@@ -87,16 +86,6 @@ func WithLanguageModelToPromptFunc(fn LanguageModelToPromptFunc) LanguageModelOp
 	}
 }
 
-// WithLanguageModelSkipUserAgent prevents per-call User-Agent overrides. This
-// exists solely for OpenRouter, which rejects User-Agent overrides.
-//
-// This function is provisional and may be removed in a future release.
-func WithLanguageModelSkipUserAgent() LanguageModelOption {
-	return func(l *languageModel) {
-		l.noDefaultUserAgent = true
-	}
-}
-
 // WithLanguageModelObjectMode sets the object generation mode.
 func WithLanguageModelObjectMode(om fantasy.ObjectMode) LanguageModelOption {
 	return func(l *languageModel) {
@@ -257,7 +246,7 @@ func (o languageModel) Generate(ctx context.Context, call fantasy.Call) (*fantas
 	if err != nil {
 		return nil, err
 	}
-	response, err := o.client.Chat.Completions.New(ctx, *params, callUARequestOptions(call, o.noDefaultUserAgent)...)
+	response, err := o.client.Chat.Completions.New(ctx, *params, callUARequestOptions(call)...)
 	if err != nil {
 		return nil, toProviderErr(err)
 	}
@@ -325,7 +314,7 @@ func (o languageModel) Stream(ctx context.Context, call fantasy.Call) (fantasy.S
 		IncludeUsage: openai.Bool(true),
 	}
 
-	stream := o.client.Chat.Completions.NewStreaming(ctx, *params, callUARequestOptions(call, o.noDefaultUserAgent)...)
+	stream := o.client.Chat.Completions.NewStreaming(ctx, *params, callUARequestOptions(call)...)
 	isActiveText := false
 	toolCalls := make(map[int64]streamToolCall)
 
@@ -744,7 +733,7 @@ func (o languageModel) generateObjectWithJSONMode(ctx context.Context, call fant
 		},
 	}
 
-	response, err := o.client.Chat.Completions.New(ctx, *params, objectCallUARequestOptions(call, o.noDefaultUserAgent)...)
+	response, err := o.client.Chat.Completions.New(ctx, *params, objectCallUARequestOptions(call)...)
 	if err != nil {
 		return nil, toProviderErr(err)
 	}
@@ -828,7 +817,7 @@ func (o languageModel) streamObjectWithJSONMode(ctx context.Context, call fantas
 		IncludeUsage: openai.Bool(true),
 	}
 
-	stream := o.client.Chat.Completions.NewStreaming(ctx, *params, objectCallUARequestOptions(call, o.noDefaultUserAgent)...)
+	stream := o.client.Chat.Completions.NewStreaming(ctx, *params, objectCallUARequestOptions(call)...)
 
 	return func(yield func(fantasy.ObjectStreamPart) bool) {
 		if len(warnings) > 0 {

providers/openai/openai.go 🔗

@@ -32,7 +32,6 @@ type options struct {
 	useResponsesAPI      bool
 	headers              map[string]string
 	userAgent            string
-	noDefaultUserAgent   bool
 	client               option.HTTPClient
 	sdkOptions           []option.RequestOption
 	objectMode           fantasy.ObjectMode
@@ -143,18 +142,6 @@ func WithUserAgent(ua string) Option {
 	}
 }
 
-// WithSkipUserAgent prevents the provider from setting a default
-// User-Agent header, preserving the underlying SDK's own User-Agent.
-// This is needed for providers like OpenRouter whose API behaviour depends
-// on the User-Agent matching the SDK that is making the request.
-//
-// This function is provisional and may be removed in a future release.
-func WithSkipUserAgent() Option {
-	return func(o *options) {
-		o.noDefaultUserAgent = true
-	}
-}
-
 // WithObjectMode sets the object generation mode.
 func WithObjectMode(om fantasy.ObjectMode) Option {
 	return func(o *options) {
@@ -178,19 +165,10 @@ func (o *provider) LanguageModel(_ context.Context, modelID string) (fantasy.Lan
 		openaiClientOptions = append(openaiClientOptions, option.WithBaseURL(o.options.baseURL))
 	}
 
-	if o.options.noDefaultUserAgent {
-		for key, value := range o.options.headers {
-			openaiClientOptions = append(openaiClientOptions, option.WithHeader(key, value))
-		}
-		if o.options.userAgent != "" {
-			openaiClientOptions = append(openaiClientOptions, option.WithHeader("User-Agent", o.options.userAgent))
-		}
-	} else {
-		defaultUA := httpheaders.DefaultUserAgent(fantasy.Version)
-		resolved := httpheaders.ResolveHeaders(o.options.headers, o.options.userAgent, defaultUA)
-		for key, value := range resolved {
-			openaiClientOptions = append(openaiClientOptions, option.WithHeader(key, value))
-		}
+	defaultUA := httpheaders.DefaultUserAgent(fantasy.Version)
+	resolved := httpheaders.ResolveHeaders(o.options.headers, o.options.userAgent, defaultUA)
+	for key, value := range resolved {
+		openaiClientOptions = append(openaiClientOptions, option.WithHeader(key, value))
 	}
 
 	if o.options.client != nil {
@@ -207,14 +185,11 @@ func (o *provider) LanguageModel(_ context.Context, modelID string) (fantasy.Lan
 		if objectMode == fantasy.ObjectModeJSON {
 			objectMode = fantasy.ObjectModeAuto
 		}
-		return newResponsesLanguageModel(modelID, o.options.name, client, objectMode, o.options.noDefaultUserAgent), nil
+		return newResponsesLanguageModel(modelID, o.options.name, client, objectMode), nil
 	}
 
 	languageModelOptions := append([]LanguageModelOption{}, o.options.languageModelOptions...)
 	languageModelOptions = append(languageModelOptions, WithLanguageModelObjectMode(o.options.objectMode))
-	if o.options.noDefaultUserAgent {
-		languageModelOptions = append(languageModelOptions, WithLanguageModelSkipUserAgent())
-	}
 
 	return newLanguageModel(
 		modelID,

providers/openai/responses_language_model.go 🔗

@@ -22,21 +22,19 @@ import (
 const topLogprobsMax = 20
 
 type responsesLanguageModel struct {
-	provider           string
-	modelID            string
-	client             openai.Client
-	objectMode         fantasy.ObjectMode
-	noDefaultUserAgent bool
+	provider   string
+	modelID    string
+	client     openai.Client
+	objectMode fantasy.ObjectMode
 }
 
 // newResponsesLanguageModel implements a responses api model.
-func newResponsesLanguageModel(modelID string, provider string, client openai.Client, objectMode fantasy.ObjectMode, noDefaultUserAgent bool) responsesLanguageModel {
+func newResponsesLanguageModel(modelID string, provider string, client openai.Client, objectMode fantasy.ObjectMode) responsesLanguageModel {
 	return responsesLanguageModel{
-		modelID:            modelID,
-		provider:           provider,
-		client:             client,
-		objectMode:         objectMode,
-		noDefaultUserAgent: noDefaultUserAgent,
+		modelID:    modelID,
+		provider:   provider,
+		client:     client,
+		objectMode: objectMode,
 	}
 }
 
@@ -771,7 +769,7 @@ func (o responsesLanguageModel) Generate(ctx context.Context, call fantasy.Call)
 		return nil, err
 	}
 
-	response, err := o.client.Responses.New(ctx, *params, callUARequestOptions(call, o.noDefaultUserAgent)...)
+	response, err := o.client.Responses.New(ctx, *params, callUARequestOptions(call)...)
 	if err != nil {
 		return nil, toProviderErr(err)
 	}
@@ -922,7 +920,7 @@ func (o responsesLanguageModel) Stream(ctx context.Context, call fantasy.Call) (
 		return nil, err
 	}
 
-	stream := o.client.Responses.NewStreaming(ctx, *params, callUARequestOptions(call, o.noDefaultUserAgent)...)
+	stream := o.client.Responses.NewStreaming(ctx, *params, callUARequestOptions(call)...)
 
 	finishReason := fantasy.FinishReasonUnknown
 	var usage fantasy.Usage
@@ -1329,7 +1327,7 @@ func (o responsesLanguageModel) generateObjectWithJSONMode(ctx context.Context,
 	}
 
 	// Make request
-	response, err := o.client.Responses.New(ctx, *params, objectCallUARequestOptions(call, o.noDefaultUserAgent)...)
+	response, err := o.client.Responses.New(ctx, *params, objectCallUARequestOptions(call)...)
 	if err != nil {
 		return nil, toProviderErr(err)
 	}
@@ -1432,7 +1430,7 @@ func (o responsesLanguageModel) streamObjectWithJSONMode(ctx context.Context, ca
 		Format: responses.ResponseFormatTextConfigParamOfJSONSchema(schemaName, jsonSchemaMap),
 	}
 
-	stream := o.client.Responses.NewStreaming(ctx, *params, objectCallUARequestOptions(call, o.noDefaultUserAgent)...)
+	stream := o.client.Responses.NewStreaming(ctx, *params, objectCallUARequestOptions(call)...)
 
 	return func(yield func(fantasy.ObjectStreamPart) bool) {
 		if len(warnings) > 0 {

providers/openrouter/openrouter.go 🔗

@@ -31,7 +31,6 @@ func New(opts ...Option) (fantasy.Provider, error) {
 		openaiOptions: []openai.Option{
 			openai.WithName(Name),
 			openai.WithBaseURL(DefaultURL),
-			openai.WithSkipUserAgent(),
 		},
 		languageModelOptions: []openai.LanguageModelOption{
 			openai.WithLanguageModelPrepareCallFunc(languagePrepareModelCall),

providers/openrouter/useragent_test.go 🔗

@@ -57,7 +57,7 @@ func TestUserAgent(t *testing.T) {
 		_, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
 
 		require.Len(t, *captured, 1)
-		assert.True(t, strings.HasPrefix((*captured)[0]["User-Agent"], "OpenAI/Go"))
+		assert.True(t, strings.HasPrefix((*captured)[0]["User-Agent"], "Charm-Fantasy/"))
 	})
 
 	t.Run("WithUserAgent wins over default", func(t *testing.T) {