fix(provider): align stream user-agent and header precedence docs

This commit is contained in:
lc6464 2026-04-07 15:41:01 +08:00
parent 7ffcd294c4
commit 92486c453f
No known key found for this signature in database
GPG key ID: 53C61B42FEC71D6D
4 changed files with 12 additions and 3 deletions

View file

@ -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) | | `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` | | `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 | | `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 | | `rpm` | int | No | Per-minute request rate limit |
| `fallbacks` | string[] | No | Fallback model names for automatic failover | | `fallbacks` | string[] | No | Fallback model names for automatic failover |
| `enabled` | bool | No | Whether this model entry is active (default: `true`) | | `enabled` | bool | No | Whether this model entry is active (default: `true`) |

View file

@ -118,7 +118,7 @@
| `max_tokens_field` | string | 否 | 覆盖请求体中 max tokens 的字段名(如 o1 模型使用 `max_completion_tokens` | | `max_tokens_field` | string | 否 | 覆盖请求体中 max tokens 的字段名(如 o1 模型使用 `max_completion_tokens` |
| `thinking_level` | string | 否 | 扩展思考级别:`off``low``medium``high``xhigh``adaptive` | | `thinking_level` | string | 否 | 扩展思考级别:`off``low``medium``high``xhigh``adaptive` |
| `extra_body` | object | 否 | 注入到每个请求体中的额外字段 | | `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 | 否 | 每分钟请求速率限制 | | `rpm` | int | 否 | 每分钟请求速率限制 |
| `fallbacks` | string[] | 否 | 自动故障转移的备用模型名称 | | `fallbacks` | string[] | 否 | 自动故障转移的备用模型名称 |
| `enabled` | bool | 否 | 是否启用此模型条目(默认:`true` | | `enabled` | bool | 否 | 是否启用此模型条目(默认:`true` |

View file

@ -271,6 +271,9 @@ func (p *Provider) ChatStream(
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
req.Header.Set("Accept", "text/event-stream") req.Header.Set("Accept", "text/event-stream")
if p.userAgent != "" {
req.Header.Set("User-Agent", p.userAgent)
}
if p.apiKey != "" { if p.apiKey != "" {
req.Header.Set("Authorization", "Bearer "+p.apiKey) req.Header.Set("Authorization", "Bearer "+p.apiKey)
} }

View file

@ -765,11 +765,12 @@ func TestProviderChat_CustomHeadersInjected(t *testing.T) {
} }
func TestProviderChatStream_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) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
gotSource = r.Header.Get("X-Source") gotSource = r.Header.Get("X-Source")
gotAuth = r.Header.Get("Authorization") gotAuth = r.Header.Get("Authorization")
gotUserAgent = r.Header.Get("User-Agent")
w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Content-Type", "text/event-stream")
_, _ = w.Write([]byte("data: {\"choices\":[{\"delta\":{\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n\n")) _, _ = w.Write([]byte("data: {\"choices\":[{\"delta\":{\"content\":\"ok\"},\"finish_reason\":\"stop\"}]}\n\n"))
@ -781,9 +782,11 @@ func TestProviderChatStream_CustomHeadersInjected(t *testing.T) {
"key", "key",
server.URL, server.URL,
"", "",
WithUserAgent("PicoClaw/Test"),
WithCustomHeaders(map[string]string{ WithCustomHeaders(map[string]string{
"X-Source": "coding-plan", "X-Source": "coding-plan",
"Authorization": "Token stream-auth", "Authorization": "Token stream-auth",
"User-Agent": "Custom-UA/Stream",
}), }),
) )
@ -807,6 +810,9 @@ func TestProviderChatStream_CustomHeadersInjected(t *testing.T) {
if gotAuth != "Token stream-auth" { if gotAuth != "Token stream-auth" {
t.Fatalf("Authorization = %q, want %q", 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) type roundTripperFunc func(*http.Request) (*http.Response, error)