Detailed changes
@@ -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)}
}
@@ -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 {
@@ -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,
@@ -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 {
@@ -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),
@@ -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) {