Detailed changes
@@ -139,7 +139,6 @@ type agentSettings struct {
frequencyPenalty *float64
headers map[string]string
userAgent string
- modelSegment string
providerOptions ProviderOptions
// TODO: add support for provider tools
@@ -451,7 +450,6 @@ func (a *agent) Generate(ctx context.Context, opts AgentCall) (*AgentResult, err
Tools: preparedTools,
ToolChoice: &stepToolChoice,
UserAgent: a.settings.userAgent,
- ModelSegment: a.settings.modelSegment,
ProviderOptions: opts.ProviderOptions,
})
})
@@ -834,7 +832,6 @@ func (a *agent) Stream(ctx context.Context, opts AgentStreamCall) (*AgentResult,
Tools: preparedTools,
ToolChoice: &stepToolChoice,
UserAgent: a.settings.userAgent,
- ModelSegment: a.settings.modelSegment,
ProviderOptions: call.ProviderOptions,
}
@@ -1432,16 +1429,6 @@ func WithUserAgent(ua string) AgentOption {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent
-// header. The default UA becomes "Fantasy/<version> (<segment>)". An empty
-// string clears any previously set segment. This is overridden by WithUserAgent
-// at either the agent or provider level.
-func WithModelSegment(segment string) AgentOption {
- return func(s *agentSettings) {
- s.modelSegment = segment
- }
-}
-
// WithProviderOptions sets the provider options for the agent.
func WithProviderOptions(providerOptions ProviderOptions) AgentOption {
return func(s *agentSettings) {
@@ -26,28 +26,6 @@ func TestAgent_WithUserAgent_PropagatesOnGenerate(t *testing.T) {
_, err := agent.Generate(context.Background(), AgentCall{Prompt: "hi"})
require.NoError(t, err)
assert.Equal(t, "MyApp/2.0", capturedCall.UserAgent)
- assert.Empty(t, capturedCall.ModelSegment)
-}
-
-func TestAgent_WithModelSegment_PropagatesOnGenerate(t *testing.T) {
- t.Parallel()
-
- var capturedCall Call
- model := &mockLanguageModel{
- generateFunc: func(_ context.Context, call Call) (*Response, error) {
- capturedCall = call
- return &Response{
- Content: []Content{TextContent{Text: "ok"}},
- FinishReason: FinishReasonStop,
- }, nil
- },
- }
-
- agent := NewAgent(model, WithModelSegment("Claude 4.6 Opus"))
- _, err := agent.Generate(context.Background(), AgentCall{Prompt: "hi"})
- require.NoError(t, err)
- assert.Empty(t, capturedCall.UserAgent)
- assert.Equal(t, "Claude 4.6 Opus", capturedCall.ModelSegment)
}
func TestAgent_WithUserAgent_PropagatesOnStream(t *testing.T) {
@@ -72,28 +50,6 @@ func TestAgent_WithUserAgent_PropagatesOnStream(t *testing.T) {
assert.Equal(t, "StreamApp/1.0", capturedCall.UserAgent)
}
-func TestAgent_WithModelSegment_PropagatesOnStream(t *testing.T) {
- t.Parallel()
-
- var capturedCall Call
- model := &mockLanguageModel{
- streamFunc: func(_ context.Context, call Call) (StreamResponse, error) {
- capturedCall = call
- return func(yield func(StreamPart) bool) {
- yield(StreamPart{
- Type: StreamPartTypeFinish,
- FinishReason: FinishReasonStop,
- })
- }, nil
- },
- }
-
- agent := NewAgent(model, WithModelSegment("GPT-5"))
- _, err := agent.Stream(context.Background(), AgentStreamCall{Prompt: "hi"})
- require.NoError(t, err)
- assert.Equal(t, "GPT-5", capturedCall.ModelSegment)
-}
-
func TestAgent_NoUA_OmitsCallLevelFields(t *testing.T) {
t.Parallel()
@@ -112,26 +68,4 @@ func TestAgent_NoUA_OmitsCallLevelFields(t *testing.T) {
_, err := agent.Generate(context.Background(), AgentCall{Prompt: "hi"})
require.NoError(t, err)
assert.Empty(t, capturedCall.UserAgent)
- assert.Empty(t, capturedCall.ModelSegment)
-}
-
-func TestAgent_WithUserAgentAndModelSegment_BothPropagate(t *testing.T) {
- t.Parallel()
-
- var capturedCall Call
- model := &mockLanguageModel{
- generateFunc: func(_ context.Context, call Call) (*Response, error) {
- capturedCall = call
- return &Response{
- Content: []Content{TextContent{Text: "ok"}},
- FinishReason: FinishReasonStop,
- }, nil
- },
- }
-
- agent := NewAgent(model, WithUserAgent("App/1.0"), WithModelSegment("Claude 4.6"))
- _, err := agent.Generate(context.Background(), AgentCall{Prompt: "hi"})
- require.NoError(t, err)
- assert.Equal(t, "App/1.0", capturedCall.UserAgent)
- assert.Equal(t, "Claude 4.6", capturedCall.ModelSegment)
}
@@ -220,8 +220,6 @@ type Call struct {
// UserAgent overrides the provider-level User-Agent header for this call.
UserAgent string `json:"-"`
- // ModelSegment overrides the provider-level model segment for this call.
- ModelSegment string `json:"-"`
// for provider specific options, the key is the provider id
ProviderOptions ProviderOptions `json:"provider_options"`
@@ -43,8 +43,6 @@ type ObjectCall struct {
// UserAgent overrides the provider-level User-Agent header for this call.
UserAgent string `json:"-"`
- // ModelSegment overrides the provider-level model segment for this call.
- ModelSegment string `json:"-"`
ProviderOptions ProviderOptions
@@ -37,7 +37,6 @@ type options struct {
name string
headers map[string]string
userAgent string
- modelSegment string
client option.HTTPClient
vertexProject string
@@ -136,15 +135,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.modelSegment = model
- }
-}
-
// WithObjectMode sets the object generation mode.
func WithObjectMode(om fantasy.ObjectMode) Option {
return func(o *options) {
@@ -166,7 +156,7 @@ func (a *provider) LanguageModel(ctx context.Context, modelID string) (fantasy.L
if a.options.baseURL != "" {
clientOptions = append(clientOptions, option.WithBaseURL(a.options.baseURL))
}
- defaultUA := httpheaders.DefaultUserAgent(fantasy.Version, a.options.modelSegment)
+ defaultUA := httpheaders.DefaultUserAgent(fantasy.Version)
resolved := httpheaders.ResolveHeaders(a.options.headers, a.options.userAgent, defaultUA)
for key, value := range resolved {
clientOptions = append(clientOptions, option.WithHeader(key, value))
@@ -7,7 +7,7 @@ import (
)
func callUARequestOptions(call fantasy.Call) []option.RequestOption {
- if ua, ok := httpheaders.CallUserAgent(fantasy.Version, call.UserAgent, call.ModelSegment); ok {
+ if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
return []option.RequestOption{option.WithHeader("User-Agent", ua)}
}
return nil
@@ -52,34 +52,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "Charm Fantasy/"+fantasy.Version, (*captured)[0]["User-Agent"])
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(WithAPIKey("k"), WithBaseURL(server.URL), WithModelSegment("Claude 4.6 Opus"))
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "claude-sonnet-4-20250514")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)", (*captured)[0]["User-Agent"])
- })
-
- t.Run("WithHeaders User-Agent wins over default", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(WithAPIKey("k"), WithBaseURL(server.URL), WithHeaders(map[string]string{"User-Agent": "custom-from-headers"}))
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "claude-sonnet-4-20250514")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "custom-from-headers", (*captured)[0]["User-Agent"])
- })
-
t.Run("WithUserAgent wins over both", func(t *testing.T) {
t.Parallel()
server, captured := newUAServer()
@@ -98,23 +70,4 @@ func TestUserAgent(t *testing.T) {
require.Len(t, *captured, 1)
assert.Equal(t, "explicit-ua", (*captured)[0]["User-Agent"])
})
-
- t.Run("WithModelSegment empty clears segment", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(
- WithAPIKey("k"),
- WithBaseURL(server.URL),
- WithModelSegment("initial"),
- WithModelSegment(""),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "claude-sonnet-4-20250514")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version, (*captured)[0]["User-Agent"])
- })
}
@@ -117,15 +117,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.openaiOptions = append(o.openaiOptions, openai.WithModelSegment(model))
- }
-}
-
// WithUseResponsesAPI configures the provider to use the responses API for models that support it.
func WithUseResponsesAPI() Option {
return func(o *options) {
@@ -52,20 +52,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "Charm Fantasy/"+fantasy.Version, (*captured)[0]["User-Agent"])
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(WithAPIKey("k"), WithBaseURL(server.URL), WithModelSegment("Claude 4.6 Opus"))
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)", (*captured)[0]["User-Agent"])
- })
-
t.Run("WithUserAgent wins over default", func(t *testing.T) {
t.Parallel()
server, captured := newUAServer()
@@ -65,15 +65,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.anthropicOptions = append(o.anthropicOptions, anthropic.WithModelSegment(model))
- }
-}
-
// WithSkipAuth configures whether to skip authentication for the Bedrock provider.
func WithSkipAuth(skipAuth bool) Option {
return func(o *options) {
@@ -56,25 +56,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "Charm Fantasy/"+fantasy.Version, (*captured)[0]["User-Agent"])
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(
- WithAPIKey("k"),
- WithSkipAuth(true),
- WithHTTPClient(&http.Client{Transport: redirectTransport(server.URL)}),
- WithModelSegment("Claude 4.6 Opus"),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "us.anthropic.claude-sonnet-4-20250514-v1:0")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)", (*captured)[0]["User-Agent"])
- })
-
t.Run("WithUserAgent wins over default", func(t *testing.T) {
t.Parallel()
server, captured := newUAServer()
@@ -11,14 +11,14 @@ import (
type callUAKey struct{}
func withCallUA(ctx context.Context, call fantasy.Call) context.Context {
- if ua, ok := httpheaders.CallUserAgent(fantasy.Version, call.UserAgent, call.ModelSegment); ok {
+ if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
return context.WithValue(ctx, callUAKey{}, ua)
}
return ctx
}
func withObjectCallUA(ctx context.Context, call fantasy.ObjectCall) context.Context {
- if ua, ok := httpheaders.CallUserAgent(fantasy.Version, call.UserAgent, call.ModelSegment); ok {
+ if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
return context.WithValue(ctx, callUAKey{}, ua)
}
return ctx
@@ -38,7 +38,6 @@ type options struct {
baseURL string
headers map[string]string
userAgent string
- modelSegment string
client *http.Client
backend genai.Backend
project string
@@ -143,15 +142,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.modelSegment = model
- }
-}
-
// WithObjectMode sets the object generation mode for the Google provider.
func WithObjectMode(om fantasy.ObjectMode) Option {
return func(o *options) {
@@ -182,9 +172,6 @@ func (a *provider) LanguageModel(ctx context.Context, modelID string) (fantasy.L
if a.options.userAgent != "" {
anthropicOpts = append(anthropicOpts, anthropic.WithUserAgent(a.options.userAgent))
}
- if a.options.modelSegment != "" {
- anthropicOpts = append(anthropicOpts, anthropic.WithModelSegment(a.options.modelSegment))
- }
p, err := anthropic.New(anthropicOpts...)
if err != nil {
return nil, err
@@ -207,7 +194,7 @@ func (a *provider) LanguageModel(ctx context.Context, modelID string) (fantasy.L
}
}
- defaultUA := httpheaders.DefaultUserAgent(fantasy.Version, a.options.modelSegment)
+ defaultUA := httpheaders.DefaultUserAgent(fantasy.Version)
resolved := httpheaders.ResolveHeaders(a.options.headers, a.options.userAgent, defaultUA)
headers := http.Header{}
@@ -83,26 +83,6 @@ func TestUserAgent(t *testing.T) {
assert.True(t, findUA(captured, "Charm Fantasy/"+fantasy.Version))
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(
- WithVertex("test-project", "us-central1"),
- WithBaseURL(server.URL),
- WithSkipAuth(true),
- WithModelSegment("Claude 4.6 Opus"),
- )
- require.NoError(t, err)
- model, err := p.LanguageModel(t.Context(), "gemini-2.0-flash")
- require.NoError(t, err)
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.NotEmpty(t, *captured)
- assert.True(t, findUA(captured, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)"))
- })
-
t.Run("WithUserAgent wins over default", func(t *testing.T) {
t.Parallel()
server, captured := newUAServer()
@@ -163,25 +143,4 @@ func TestUserAgent(t *testing.T) {
require.NotEmpty(t, *captured)
assert.True(t, findUA(captured, "explicit-ua"))
})
-
- t.Run("WithModelSegment empty clears segment", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(
- WithVertex("test-project", "us-central1"),
- WithBaseURL(server.URL),
- WithSkipAuth(true),
- WithModelSegment("initial"),
- WithModelSegment(""),
- )
- require.NoError(t, err)
- model, err := p.LanguageModel(t.Context(), "gemini-2.0-flash")
- require.NoError(t, err)
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.NotEmpty(t, *captured)
- assert.True(t, findUA(captured, "Charm Fantasy/"+fantasy.Version))
- })
}
@@ -1,23 +1,12 @@
// Package httpheaders provides shared User-Agent resolution for all HTTP-based providers.
package httpheaders
-import (
- "strings"
- "unicode"
-)
-
-const maxAgentLength = 64
+import "strings"
// DefaultUserAgent returns the default User-Agent string for the SDK.
-// If agent is non-empty, the result is "Charm Fantasy/<version> (<agent>)".
-// Otherwise, the result is "Charm Fantasy/<version>".
-func DefaultUserAgent(version, agent string) string {
- const sdk = "Charm Fantasy/"
- agent = sanitizeAgent(agent)
- if agent == "" {
- return sdk + version
- }
- return sdk + version + " (" + agent + ")"
+// The result is "Charm Fantasy/<version>".
+func DefaultUserAgent(version string) string {
+ return "Charm Fantasy/" + version
}
// ResolveHeaders returns a new header map with User-Agent resolved according to precedence:
@@ -55,35 +44,9 @@ func ResolveHeaders(headers map[string]string, explicitUA, defaultUA string) map
// CallUserAgent resolves the User-Agent for a single API call. It returns the
// resolved UA string and true if a per-call override should be applied, or
// empty string and false if the client-level UA should be used as-is.
-//
-// Precedence:
-// 1. callUA (agent-level WithUserAgent) — highest
-// 2. callSegment used to build default UA (agent-level WithModelSegment)
-// 3. empty — use client-level UA (return false)
-func CallUserAgent(version, callUA, callSegment string) (string, bool) {
+func CallUserAgent(callUA string) (string, bool) {
if callUA != "" {
return callUA, true
}
- if callSegment != "" {
- return DefaultUserAgent(version, callSegment), true
- }
return "", false
}
-
-func sanitizeAgent(s string) string {
- s = strings.TrimSpace(s)
- var b strings.Builder
- b.Grow(len(s))
- count := 0
- for _, r := range s {
- if r < 0x20 || r == '(' || r == ')' {
- continue
- }
- if count >= maxAgentLength {
- break
- }
- b.WriteRune(r)
- count++
- }
- return strings.TrimRightFunc(b.String(), unicode.IsSpace)
-}
@@ -1,7 +1,6 @@
package httpheaders
import (
- "strings"
"testing"
"github.com/stretchr/testify/assert"
@@ -14,22 +13,16 @@ func TestDefaultUserAgent(t *testing.T) {
tests := []struct {
name string
version string
- agent string
want string
}{
- {name: "no agent", version: "0.11.0", agent: "", want: "Charm Fantasy/0.11.0"},
- {name: "with agent", version: "0.11.0", agent: "Claude 4.6 Opus", want: "Charm Fantasy/0.11.0 (Claude 4.6 Opus)"},
- {name: "agent trimmed", version: "1.0.0", agent: " spaces ", want: "Charm Fantasy/1.0.0 (spaces)"},
- {name: "agent strips parens", version: "1.0.0", agent: "foo(bar)", want: "Charm Fantasy/1.0.0 (foobar)"},
- {name: "agent strips control chars", version: "1.0.0", agent: "foo\x01bar", want: "Charm Fantasy/1.0.0 (foobar)"},
- {name: "agent capped at 64 chars", version: "1.0.0", agent: strings.Repeat("a", 100), want: "Charm Fantasy/1.0.0 (" + strings.Repeat("a", 64) + ")"},
- {name: "whitespace-only agent treated as empty", version: "1.0.0", agent: " ", want: "Charm Fantasy/1.0.0"},
+ {name: "basic version", version: "0.11.0", want: "Charm Fantasy/0.11.0"},
+ {name: "another version", version: "1.0.0", want: "Charm Fantasy/1.0.0"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- got := DefaultUserAgent(tt.version, tt.agent)
+ got := DefaultUserAgent(tt.version)
assert.Equal(t, tt.want, got)
})
}
@@ -99,34 +92,6 @@ func TestResolveHeaders_PreservesOtherHeaders(t *testing.T) {
assert.Equal(t, "Charm Fantasy/1.0.0", got["User-Agent"])
}
-func TestSanitizeAgent(t *testing.T) {
- t.Parallel()
-
- tests := []struct {
- name string
- input string
- want string
- }{
- {name: "normal text", input: "Claude 4.6 Opus", want: "Claude 4.6 Opus"},
- {name: "leading trailing spaces", input: " spaced ", want: "spaced"},
- {name: "parentheses removed", input: "agent(v2)", want: "agentv2"},
- {name: "control chars removed", input: "a\x00b\x1fc", want: "abc"},
- {name: "capped at 64", input: strings.Repeat("x", 100), want: strings.Repeat("x", 64)},
- {name: "multibyte runes capped at 64 chars", input: strings.Repeat("é", 100), want: strings.Repeat("é", 64)},
- {name: "empty stays empty", input: "", want: ""},
- {name: "only spaces", input: " ", want: ""},
- {name: "trailing space after cap", input: strings.Repeat("a", 63) + " b", want: strings.Repeat("a", 63)},
- }
-
- for _, tt := range tests {
- t.Run(tt.name, func(t *testing.T) {
- t.Parallel()
- got := sanitizeAgent(tt.input)
- assert.Equal(t, tt.want, got)
- })
- }
-}
-
func TestResolveHeaders_DuplicateCaseInsensitiveKeys(t *testing.T) {
t.Parallel()
@@ -144,21 +109,18 @@ func TestCallUserAgent(t *testing.T) {
t.Parallel()
tests := []struct {
- name string
- callUA string
- callSegment string
- wantUA string
- wantOK bool
+ name string
+ callUA string
+ wantUA string
+ wantOK bool
}{
- {name: "no override", callUA: "", callSegment: "", wantUA: "", wantOK: false},
- {name: "explicit UA", callUA: "MyAgent/1.0", callSegment: "", wantUA: "MyAgent/1.0", wantOK: true},
- {name: "model segment only", callUA: "", callSegment: "Claude 4.6", wantUA: "Charm Fantasy/0.11.0 (Claude 4.6)", wantOK: true},
- {name: "explicit UA wins over segment", callUA: "MyAgent/1.0", callSegment: "Claude 4.6", wantUA: "MyAgent/1.0", wantOK: true},
+ {name: "no override", callUA: "", wantUA: "", wantOK: false},
+ {name: "explicit UA", callUA: "MyAgent/1.0", wantUA: "MyAgent/1.0", wantOK: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
- ua, ok := CallUserAgent("0.11.0", tt.callUA, tt.callSegment)
+ ua, ok := CallUserAgent(tt.callUA)
assert.Equal(t, tt.wantOK, ok)
assert.Equal(t, tt.wantUA, ua)
})
@@ -9,7 +9,7 @@ import (
// callUARequestOptions returns per-request options that override the
// client-level User-Agent header when the Call carries agent-level UA settings.
func callUARequestOptions(call fantasy.Call) []option.RequestOption {
- if ua, ok := httpheaders.CallUserAgent(fantasy.Version, call.UserAgent, call.ModelSegment); ok {
+ if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
return []option.RequestOption{option.WithHeader("User-Agent", ua)}
}
return nil
@@ -18,7 +18,7 @@ func callUARequestOptions(call fantasy.Call) []option.RequestOption {
// 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) []option.RequestOption {
- if ua, ok := httpheaders.CallUserAgent(fantasy.Version, call.UserAgent, call.ModelSegment); ok {
+ if ua, ok := httpheaders.CallUserAgent(call.UserAgent); ok {
return []option.RequestOption{option.WithHeader("User-Agent", ua)}
}
return nil
@@ -32,7 +32,6 @@ type options struct {
useResponsesAPI bool
headers map[string]string
userAgent string
- modelSegment string
client option.HTTPClient
sdkOptions []option.RequestOption
objectMode fantasy.ObjectMode
@@ -143,15 +142,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.modelSegment = model
- }
-}
-
// WithObjectMode sets the object generation mode.
func WithObjectMode(om fantasy.ObjectMode) Option {
return func(o *options) {
@@ -175,7 +165,7 @@ func (o *provider) LanguageModel(_ context.Context, modelID string) (fantasy.Lan
openaiClientOptions = append(openaiClientOptions, option.WithBaseURL(o.options.baseURL))
}
- defaultUA := httpheaders.DefaultUserAgent(fantasy.Version, o.options.modelSegment)
+ 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))
@@ -3323,22 +3323,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "Charm Fantasy/"+fantasy.Version, server.calls[0].headers["User-Agent"])
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
-
- server := newMockServer()
- defer server.close()
- server.prepareJSONResponse(map[string]any{})
-
- p, err := New(WithAPIKey("k"), WithBaseURL(server.server.URL), WithModelSegment("Claude 4.6 Opus"))
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: testPrompt})
-
- require.Len(t, server.calls, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)", server.calls[0].headers["User-Agent"])
- })
-
t.Run("WithHeaders User-Agent wins over default", func(t *testing.T) {
t.Parallel()
@@ -3376,72 +3360,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "explicit-ua", server.calls[0].headers["User-Agent"])
})
- t.Run("WithModelSegment empty clears segment", func(t *testing.T) {
- t.Parallel()
-
- server := newMockServer()
- defer server.close()
- server.prepareJSONResponse(map[string]any{})
-
- p, err := New(
- WithAPIKey("k"),
- WithBaseURL(server.server.URL),
- WithModelSegment("initial"),
- WithModelSegment(""),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: testPrompt})
-
- require.Len(t, server.calls, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version, server.calls[0].headers["User-Agent"])
- })
-
- t.Run("Call.UserAgent overrides provider UA", func(t *testing.T) {
- t.Parallel()
-
- server := newMockServer()
- defer server.close()
- server.prepareJSONResponse(map[string]any{})
-
- p, err := New(
- WithAPIKey("k"),
- WithBaseURL(server.server.URL),
- WithUserAgent("provider-ua"),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{
- Prompt: testPrompt,
- UserAgent: "agent-ua",
- })
-
- require.Len(t, server.calls, 1)
- assert.Equal(t, "agent-ua", server.calls[0].headers["User-Agent"])
- })
-
- t.Run("Call.ModelSegment overrides provider default", func(t *testing.T) {
- t.Parallel()
-
- server := newMockServer()
- defer server.close()
- server.prepareJSONResponse(map[string]any{})
-
- p, err := New(
- WithAPIKey("k"),
- WithBaseURL(server.server.URL),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{
- Prompt: testPrompt,
- ModelSegment: "GPT-5",
- })
-
- require.Len(t, server.calls, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (GPT-5)", server.calls[0].headers["User-Agent"])
- })
-
t.Run("Call.UserAgent overrides provider WithHeaders UA", func(t *testing.T) {
t.Parallel()
@@ -3507,27 +3425,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "agent-ua", server.calls[0].headers["User-Agent"])
})
- t.Run("agent WithModelSegment overrides provider default end-to-end", func(t *testing.T) {
- t.Parallel()
-
- server := newMockServer()
- defer server.close()
- server.prepareJSONResponse(map[string]any{})
-
- p, err := New(
- WithAPIKey("k"),
- WithBaseURL(server.server.URL),
- )
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "gpt-4")
-
- agent := fantasy.NewAgent(model, fantasy.WithModelSegment("Claude 4.6"))
- _, _ = agent.Generate(t.Context(), fantasy.AgentCall{Prompt: "hi"})
-
- require.Len(t, server.calls, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6)", server.calls[0].headers["User-Agent"])
- })
-
t.Run("agent without UA falls through to provider UA end-to-end", func(t *testing.T) {
t.Parallel()
@@ -116,15 +116,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.openaiOptions = append(o.openaiOptions, openai.WithModelSegment(model))
- }
-}
-
// WithUseResponsesAPI configures the provider to use the responses API for models that support it.
func WithUseResponsesAPI() Option {
return func(o *options) {
@@ -97,15 +97,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.openaiOptions = append(o.openaiOptions, openai.WithModelSegment(model))
- }
-}
-
// WithObjectMode sets the object generation mode for the OpenRouter provider.
// Supported modes: ObjectModeTool, ObjectModeText.
// ObjectModeAuto and ObjectModeJSON are automatically converted to ObjectModeTool
@@ -59,20 +59,6 @@ func TestUserAgent(t *testing.T) {
assert.Equal(t, "Charm Fantasy/"+fantasy.Version, (*captured)[0]["User-Agent"])
})
- t.Run("model segment format", func(t *testing.T) {
- t.Parallel()
- server, captured := newUAServer()
- defer server.Close()
-
- p, err := New(WithAPIKey("k"), withBaseURL(server.URL), WithModelSegment("Claude 4.6 Opus"))
- require.NoError(t, err)
- model, _ := p.LanguageModel(t.Context(), "openai/gpt-4")
- _, _ = model.Generate(t.Context(), fantasy.Call{Prompt: prompt})
-
- require.Len(t, *captured, 1)
- assert.Equal(t, "Charm Fantasy/"+fantasy.Version+" (Claude 4.6 Opus)", (*captured)[0]["User-Agent"])
- })
-
t.Run("WithUserAgent wins over default", func(t *testing.T) {
t.Parallel()
server, captured := newUAServer()
@@ -104,15 +104,6 @@ func WithUserAgent(ua string) Option {
}
}
-// WithModelSegment sets the model segment appended to the default User-Agent.
-// The resulting header is "Fantasy/<version> (<model>)". Pass an empty string
-// to clear a previously set segment.
-func WithModelSegment(model string) Option {
- return func(o *options) {
- o.openaiOptions = append(o.openaiOptions, openai.WithModelSegment(model))
- }
-}
-
// WithSDKOptions sets the SDK options for the Vercel provider.
func WithSDKOptions(opts ...option.RequestOption) Option {
return func(o *options) {