From 43df1e4fba97e6b87281818697133118ab811735 Mon Sep 17 00:00:00 2001 From: Andrey Nering Date: Fri, 13 Mar 2026 10:17:27 -0300 Subject: [PATCH] chore: revert change to skip user-agent for openrouter --- 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(-) diff --git a/providers/openai/call_useragent.go b/providers/openai/call_useragent.go index 777a19d3fb7723bc9e29e04045bb1a106044036f..fe9b4822de40a1b4b8a54915d74f302c1c93a7f9 100644 --- a/providers/openai/call_useragent.go +++ b/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)} } diff --git a/providers/openai/language_model.go b/providers/openai/language_model.go index 0679aa3f5bcde82adfe30d83330f8438136ccb9c..0445a610455c0a8225206077e0fd231bb504aa25 100644 --- a/providers/openai/language_model.go +++ b/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 { diff --git a/providers/openai/openai.go b/providers/openai/openai.go index fb9e7838dde7b5d1cbeac5e93f5568230f5452e0..51ebf4845ec9b6efeab86bb265c029ff39540447 100644 --- a/providers/openai/openai.go +++ b/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, diff --git a/providers/openai/responses_language_model.go b/providers/openai/responses_language_model.go index f24dd07c1a8ee48db026658da5b4ee2ffaf93a9d..5f22735f7a6c84fd55ba7d8d979a3ced617d66e0 100644 --- a/providers/openai/responses_language_model.go +++ b/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 { diff --git a/providers/openrouter/openrouter.go b/providers/openrouter/openrouter.go index 36b066c0af7085a6e2491933eaeacdd5887e9a40..623186bc69e5d1c31155fdde5935e7974fbc103c 100644 --- a/providers/openrouter/openrouter.go +++ b/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), diff --git a/providers/openrouter/useragent_test.go b/providers/openrouter/useragent_test.go index 14f1d17ff5b7bdf294184945896d89c1d73f7f8b..5c38db8eaf303cd2528cb4bcfb54207b4c7823b5 100644 --- a/providers/openrouter/useragent_test.go +++ b/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) {