chore(useragent): drop model detail option

Christian Rocha created

Change summary

agent.go                                           |  13 --
agent_useragent_test.go                            |  66 ----------
model.go                                           |   2 
object.go                                          |   2 
providers/anthropic/anthropic.go                   |  12 -
providers/anthropic/call_useragent.go              |   2 
providers/anthropic/useragent_test.go              |  47 -------
providers/azure/azure.go                           |   9 -
providers/azure/useragent_test.go                  |  14 --
providers/bedrock/bedrock.go                       |   9 -
providers/bedrock/useragent_test.go                |  19 --
providers/google/call_useragent.go                 |   4 
providers/google/google.go                         |  15 --
providers/google/useragent_test.go                 |  41 ------
providers/internal/httpheaders/httpheaders.go      |  47 ------
providers/internal/httpheaders/httpheaders_test.go |  58 +-------
providers/openai/call_useragent.go                 |   4 
providers/openai/openai.go                         |  12 -
providers/openai/openai_test.go                    | 103 ----------------
providers/openaicompat/openaicompat.go             |   9 -
providers/openrouter/openrouter.go                 |   9 -
providers/openrouter/useragent_test.go             |  14 --
providers/vercel/vercel.go                         |   9 -
23 files changed, 23 insertions(+), 497 deletions(-)

Detailed changes

agent.go 🔗

@@ -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) {

agent_useragent_test.go 🔗

@@ -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)
 }

model.go 🔗

@@ -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"`

object.go 🔗

@@ -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
 

providers/anthropic/anthropic.go 🔗

@@ -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))

providers/anthropic/call_useragent.go 🔗

@@ -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

providers/anthropic/useragent_test.go 🔗

@@ -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"])
-	})
 }

providers/azure/azure.go 🔗

@@ -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) {

providers/azure/useragent_test.go 🔗

@@ -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()

providers/bedrock/bedrock.go 🔗

@@ -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) {

providers/bedrock/useragent_test.go 🔗

@@ -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()

providers/google/call_useragent.go 🔗

@@ -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

providers/google/google.go 🔗

@@ -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{}

providers/google/useragent_test.go 🔗

@@ -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))
-	})
 }

providers/internal/httpheaders/httpheaders.go 🔗

@@ -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)
-}

providers/internal/httpheaders/httpheaders_test.go 🔗

@@ -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)
 		})

providers/openai/call_useragent.go 🔗

@@ -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

providers/openai/openai.go 🔗

@@ -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))

providers/openai/openai_test.go 🔗

@@ -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()
 

providers/openaicompat/openaicompat.go 🔗

@@ -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) {

providers/openrouter/openrouter.go 🔗

@@ -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

providers/openrouter/useragent_test.go 🔗

@@ -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()

providers/vercel/vercel.go 🔗

@@ -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) {