From 92486c453f17b029e3ef5770b16ab6e7a71c0a38 Mon Sep 17 00:00:00 2001 From: lc6464 <64722907+lc6464@users.noreply.github.com> Date: Tue, 7 Apr 2026 15:41:01 +0800 Subject: [PATCH] fix(provider): align stream user-agent and header precedence docs --- docs/providers.md | 2 +- docs/zh/providers.md | 2 +- pkg/providers/openai_compat/provider.go | 3 +++ pkg/providers/openai_compat/provider_test.go | 8 +++++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/docs/providers.md b/docs/providers.md index 64484adb0..d03fbab3e 100644 --- a/docs/providers.md +++ b/docs/providers.md @@ -122,7 +122,7 @@ This design also enables **multi-agent support** with flexible provider selectio | `max_tokens_field` | string | No | Override the max tokens field name in request body (e.g., `max_completion_tokens` for o1 models) | | `thinking_level` | string | No | Extended thinking level: `off`, `low`, `medium`, `high`, `xhigh`, or `adaptive` | | `extra_body` | object | No | Additional fields to inject into every request body | -| `custom_headers` | object | No | Additional HTTP headers to inject into every request (e.g., `{"X-Source":"coding-plan"}`) | +| `custom_headers` | object | No | Additional HTTP headers to inject into every request (e.g., `{"X-Source":"coding-plan"}`). If a key matches a built-in header, the custom value overrides the built-in one (e.g., `Authorization`, `User-Agent`, `Content-Type`, `Accept`). | | `rpm` | int | No | Per-minute request rate limit | | `fallbacks` | string[] | No | Fallback model names for automatic failover | | `enabled` | bool | No | Whether this model entry is active (default: `true`) | diff --git a/docs/zh/providers.md b/docs/zh/providers.md index fbbafe798..7b3930f6f 100644 --- a/docs/zh/providers.md +++ b/docs/zh/providers.md @@ -118,7 +118,7 @@ | `max_tokens_field` | string | 否 | 覆盖请求体中 max tokens 的字段名(如 o1 模型使用 `max_completion_tokens`) | | `thinking_level` | string | 否 | 扩展思考级别:`off`、`low`、`medium`、`high`、`xhigh` 或 `adaptive` | | `extra_body` | object | 否 | 注入到每个请求体中的额外字段 | -| `custom_headers` | object | 否 | 注入到每个请求中的额外 HTTP 请求头(例如 `{"X-Source":"coding-plan"}`) | +| `custom_headers` | object | 否 | 注入到每个请求中的额外 HTTP 请求头(例如 `{"X-Source":"coding-plan"}`)。若键名与内置请求头同名,会覆盖内置值(如 `Authorization`、`User-Agent`、`Content-Type`、`Accept`)。 | | `rpm` | int | 否 | 每分钟请求速率限制 | | `fallbacks` | string[] | 否 | 自动故障转移的备用模型名称 | | `enabled` | bool | 否 | 是否启用此模型条目(默认:`true`) | diff --git a/pkg/providers/openai_compat/provider.go b/pkg/providers/openai_compat/provider.go index fe46b6d92..d25a0fce4 100644 --- a/pkg/providers/openai_compat/provider.go +++ b/pkg/providers/openai_compat/provider.go @@ -271,6 +271,9 @@ func (p *Provider) ChatStream( req.Header.Set("Content-Type", "application/json") req.Header.Set("Accept", "text/event-stream") + if p.userAgent != "" { + req.Header.Set("User-Agent", p.userAgent) + } if p.apiKey != "" { req.Header.Set("Authorization", "Bearer "+p.apiKey) } diff --git a/pkg/providers/openai_compat/provider_test.go b/pkg/providers/openai_compat/provider_test.go index c3786c127..d140d63d6 100644 --- a/pkg/providers/openai_compat/provider_test.go +++ b/pkg/providers/openai_compat/provider_test.go @@ -765,11 +765,12 @@ func TestProviderChat_CustomHeadersInjected(t *testing.T) { } func TestProviderChatStream_CustomHeadersInjected(t *testing.T) { - var gotSource, gotAuth string + var gotSource, gotAuth, gotUserAgent string server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { gotSource = r.Header.Get("X-Source") gotAuth = r.Header.Get("Authorization") + gotUserAgent = r.Header.Get("User-Agent") w.Header().Set("Content-Type", "text/event-stream") _, _ = w.Write([]byte("data: {\"choices\":[{\"delta\":{\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n\n")) @@ -781,9 +782,11 @@ func TestProviderChatStream_CustomHeadersInjected(t *testing.T) { "key", server.URL, "", + WithUserAgent("PicoClaw/Test"), WithCustomHeaders(map[string]string{ "X-Source": "coding-plan", "Authorization": "Token stream-auth", + "User-Agent": "Custom-UA/Stream", }), ) @@ -807,6 +810,9 @@ func TestProviderChatStream_CustomHeadersInjected(t *testing.T) { if gotAuth != "Token stream-auth" { t.Fatalf("Authorization = %q, want %q", gotAuth, "Token stream-auth") } + if gotUserAgent != "Custom-UA/Stream" { + t.Fatalf("User-Agent = %q, want %q", gotUserAgent, "Custom-UA/Stream") + } } type roundTripperFunc func(*http.Request) (*http.Response, error)