mirror of
https://github.com/router-for-me/CLIProxyAPI.git
synced 2026-02-03 13:00:52 +08:00
feat(config): support HTTP headers across providers
This commit is contained in:
@@ -49,10 +49,10 @@ ws-auth: false
|
|||||||
# Gemini API keys (preferred)
|
# Gemini API keys (preferred)
|
||||||
#gemini-api-key:
|
#gemini-api-key:
|
||||||
# - api-key: "AIzaSy...01"
|
# - api-key: "AIzaSy...01"
|
||||||
# # base-url: "https://generativelanguage.googleapis.com"
|
# base-url: "https://generativelanguage.googleapis.com"
|
||||||
# # headers:
|
# headers:
|
||||||
# # X-Custom-Header: "custom-value"
|
# X-Custom-Header: "custom-value"
|
||||||
# # proxy-url: "socks5://proxy.example.com:1080"
|
# proxy-url: "socks5://proxy.example.com:1080"
|
||||||
# - api-key: "AIzaSy...02"
|
# - api-key: "AIzaSy...02"
|
||||||
|
|
||||||
# API keys for official Generative Language API (legacy compatibility)
|
# API keys for official Generative Language API (legacy compatibility)
|
||||||
@@ -64,6 +64,8 @@ ws-auth: false
|
|||||||
#codex-api-key:
|
#codex-api-key:
|
||||||
# - api-key: "sk-atSM..."
|
# - api-key: "sk-atSM..."
|
||||||
# base-url: "https://www.example.com" # use the custom codex API endpoint
|
# base-url: "https://www.example.com" # use the custom codex API endpoint
|
||||||
|
# headers:
|
||||||
|
# X-Custom-Header: "custom-value"
|
||||||
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
|
|
||||||
# Claude API keys
|
# Claude API keys
|
||||||
@@ -71,6 +73,8 @@ ws-auth: false
|
|||||||
# - api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
# - api-key: "sk-atSM..." # use the official claude API key, no need to set the base url
|
||||||
# - api-key: "sk-atSM..."
|
# - api-key: "sk-atSM..."
|
||||||
# base-url: "https://www.example.com" # use the custom claude API endpoint
|
# base-url: "https://www.example.com" # use the custom claude API endpoint
|
||||||
|
# headers:
|
||||||
|
# X-Custom-Header: "custom-value"
|
||||||
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
# proxy-url: "socks5://proxy.example.com:1080" # optional: per-key proxy override
|
||||||
# models:
|
# models:
|
||||||
# - name: "claude-3-5-sonnet-20241022" # upstream model name
|
# - name: "claude-3-5-sonnet-20241022" # upstream model name
|
||||||
@@ -80,6 +84,8 @@ ws-auth: false
|
|||||||
#openai-compatibility:
|
#openai-compatibility:
|
||||||
# - name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
# - name: "openrouter" # The name of the provider; it will be used in the user agent and other places.
|
||||||
# base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
# base-url: "https://openrouter.ai/api/v1" # The base URL of the provider.
|
||||||
|
# headers:
|
||||||
|
# X-Custom-Header: "custom-value"
|
||||||
# # New format with per-key proxy support (recommended):
|
# # New format with per-key proxy support (recommended):
|
||||||
# api-key-entries:
|
# api-key-entries:
|
||||||
# - api-key: "sk-or-v1-...b780"
|
# - api-key: "sk-or-v1-...b780"
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ type ClaudeKey struct {
|
|||||||
|
|
||||||
// Models defines upstream model names and aliases for request routing.
|
// Models defines upstream model names and aliases for request routing.
|
||||||
Models []ClaudeModel `yaml:"models" json:"models"`
|
Models []ClaudeModel `yaml:"models" json:"models"`
|
||||||
|
|
||||||
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClaudeModel describes a mapping between an alias and the actual upstream model name.
|
// ClaudeModel describes a mapping between an alias and the actual upstream model name.
|
||||||
@@ -123,6 +126,9 @@ type CodexKey struct {
|
|||||||
|
|
||||||
// ProxyURL overrides the global proxy setting for this API key if provided.
|
// ProxyURL overrides the global proxy setting for this API key if provided.
|
||||||
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
ProxyURL string `yaml:"proxy-url" json:"proxy-url"`
|
||||||
|
|
||||||
|
// Headers optionally adds extra HTTP headers for requests sent with this key.
|
||||||
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeminiKey represents the configuration for a Gemini API key,
|
// GeminiKey represents the configuration for a Gemini API key,
|
||||||
@@ -159,6 +165,9 @@ type OpenAICompatibility struct {
|
|||||||
|
|
||||||
// Models defines the model configurations including aliases for routing.
|
// Models defines the model configurations including aliases for routing.
|
||||||
Models []OpenAICompatibilityModel `yaml:"models" json:"models"`
|
Models []OpenAICompatibilityModel `yaml:"models" json:"models"`
|
||||||
|
|
||||||
|
// Headers optionally adds extra HTTP headers for requests sent to this provider.
|
||||||
|
Headers map[string]string `yaml:"headers,omitempty" json:"headers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting.
|
// OpenAICompatibilityAPIKey represents an API key configuration with optional proxy setting.
|
||||||
@@ -255,6 +264,9 @@ func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
|
|||||||
// Sanitize Codex keys: drop entries without base-url
|
// Sanitize Codex keys: drop entries without base-url
|
||||||
sanitizeCodexKeys(&cfg)
|
sanitizeCodexKeys(&cfg)
|
||||||
|
|
||||||
|
// Normalize Claude key headers
|
||||||
|
normalizeClaudeKeys(&cfg)
|
||||||
|
|
||||||
// Return the populated configuration struct.
|
// Return the populated configuration struct.
|
||||||
return &cfg, nil
|
return &cfg, nil
|
||||||
}
|
}
|
||||||
@@ -271,6 +283,7 @@ func sanitizeOpenAICompatibility(cfg *Config) {
|
|||||||
e := cfg.OpenAICompatibility[i]
|
e := cfg.OpenAICompatibility[i]
|
||||||
e.Name = strings.TrimSpace(e.Name)
|
e.Name = strings.TrimSpace(e.Name)
|
||||||
e.BaseURL = strings.TrimSpace(e.BaseURL)
|
e.BaseURL = strings.TrimSpace(e.BaseURL)
|
||||||
|
e.Headers = normalizeHeaders(e.Headers)
|
||||||
if e.BaseURL == "" {
|
if e.BaseURL == "" {
|
||||||
// Skip providers with no base-url; treated as removed
|
// Skip providers with no base-url; treated as removed
|
||||||
continue
|
continue
|
||||||
@@ -290,6 +303,7 @@ func sanitizeCodexKeys(cfg *Config) {
|
|||||||
for i := range cfg.CodexKey {
|
for i := range cfg.CodexKey {
|
||||||
e := cfg.CodexKey[i]
|
e := cfg.CodexKey[i]
|
||||||
e.BaseURL = strings.TrimSpace(e.BaseURL)
|
e.BaseURL = strings.TrimSpace(e.BaseURL)
|
||||||
|
e.Headers = normalizeHeaders(e.Headers)
|
||||||
if e.BaseURL == "" {
|
if e.BaseURL == "" {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -298,6 +312,16 @@ func sanitizeCodexKeys(cfg *Config) {
|
|||||||
cfg.CodexKey = out
|
cfg.CodexKey = out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizeClaudeKeys(cfg *Config) {
|
||||||
|
if cfg == nil || len(cfg.ClaudeKey) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := range cfg.ClaudeKey {
|
||||||
|
entry := &cfg.ClaudeKey[i]
|
||||||
|
entry.Headers = normalizeHeaders(entry.Headers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (cfg *Config) SyncGeminiKeys() {
|
func (cfg *Config) SyncGeminiKeys() {
|
||||||
if cfg == nil {
|
if cfg == nil {
|
||||||
return
|
return
|
||||||
@@ -313,7 +337,7 @@ func (cfg *Config) SyncGeminiKeys() {
|
|||||||
}
|
}
|
||||||
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
entry.BaseURL = strings.TrimSpace(entry.BaseURL)
|
||||||
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
entry.ProxyURL = strings.TrimSpace(entry.ProxyURL)
|
||||||
entry.Headers = normalizeGeminiHeaders(entry.Headers)
|
entry.Headers = normalizeHeaders(entry.Headers)
|
||||||
if _, exists := seen[entry.APIKey]; exists {
|
if _, exists := seen[entry.APIKey]; exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -356,7 +380,7 @@ func looksLikeBcrypt(s string) bool {
|
|||||||
return len(s) > 4 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$")
|
return len(s) > 4 && (s[:4] == "$2a$" || s[:4] == "$2b$" || s[:4] == "$2y$")
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeGeminiHeaders(headers map[string]string) map[string]string {
|
func normalizeHeaders(headers map[string]string) map[string]string {
|
||||||
if len(headers) == 0 {
|
if len(headers) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
claudeauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude"
|
claudeauth "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/claude"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/misc"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
@@ -67,7 +68,7 @@ func (e *ClaudeExecutor) Execute(ctx context.Context, auth *cliproxyauth.Auth, r
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, false)
|
applyClaudeHeaders(httpReq, auth, apiKey, false)
|
||||||
var authID, authLabel, authType, authValue string
|
var authID, authLabel, authType, authValue string
|
||||||
if auth != nil {
|
if auth != nil {
|
||||||
authID = auth.ID
|
authID = auth.ID
|
||||||
@@ -159,7 +160,7 @@ func (e *ClaudeExecutor) ExecuteStream(ctx context.Context, auth *cliproxyauth.A
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, true)
|
applyClaudeHeaders(httpReq, auth, apiKey, true)
|
||||||
var authID, authLabel, authType, authValue string
|
var authID, authLabel, authType, authValue string
|
||||||
if auth != nil {
|
if auth != nil {
|
||||||
authID = auth.ID
|
authID = auth.ID
|
||||||
@@ -290,7 +291,7 @@ func (e *ClaudeExecutor) CountTokens(ctx context.Context, auth *cliproxyauth.Aut
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return cliproxyexecutor.Response{}, err
|
return cliproxyexecutor.Response{}, err
|
||||||
}
|
}
|
||||||
applyClaudeHeaders(httpReq, apiKey, false)
|
applyClaudeHeaders(httpReq, auth, apiKey, false)
|
||||||
var authID, authLabel, authType, authValue string
|
var authID, authLabel, authType, authValue string
|
||||||
if auth != nil {
|
if auth != nil {
|
||||||
authID = auth.ID
|
authID = auth.ID
|
||||||
@@ -529,7 +530,7 @@ func decodeResponseBody(body io.ReadCloser, contentEncoding string) (io.ReadClos
|
|||||||
return body, nil
|
return body, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyClaudeHeaders(r *http.Request, apiKey string, stream bool) {
|
func applyClaudeHeaders(r *http.Request, auth *cliproxyauth.Auth, apiKey string, stream bool) {
|
||||||
r.Header.Set("Authorization", "Bearer "+apiKey)
|
r.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
r.Header.Set("Content-Type", "application/json")
|
r.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
@@ -564,9 +565,14 @@ func applyClaudeHeaders(r *http.Request, apiKey string, stream bool) {
|
|||||||
r.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
|
r.Header.Set("Accept-Encoding", "gzip, deflate, br, zstd")
|
||||||
if stream {
|
if stream {
|
||||||
r.Header.Set("Accept", "text/event-stream")
|
r.Header.Set("Accept", "text/event-stream")
|
||||||
return
|
} else {
|
||||||
|
r.Header.Set("Accept", "application/json")
|
||||||
}
|
}
|
||||||
r.Header.Set("Accept", "application/json")
|
var attrs map[string]string
|
||||||
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
|
}
|
||||||
|
util.ApplyCustomHeadersFromAttrs(r, attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func claudeCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
func claudeCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
||||||
|
|||||||
@@ -585,6 +585,11 @@ func applyCodexHeaders(r *http.Request, auth *cliproxyauth.Auth, token string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
var attrs map[string]string
|
||||||
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
|
}
|
||||||
|
util.ApplyCustomHeadersFromAttrs(r, attrs)
|
||||||
}
|
}
|
||||||
|
|
||||||
func codexCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
func codexCreds(a *cliproxyauth.Auth) (apiKey, baseURL string) {
|
||||||
|
|||||||
@@ -495,44 +495,11 @@ func resolveGeminiBaseURL(auth *cliproxyauth.Auth) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func applyGeminiHeaders(req *http.Request, auth *cliproxyauth.Auth) {
|
func applyGeminiHeaders(req *http.Request, auth *cliproxyauth.Auth) {
|
||||||
if req == nil {
|
var attrs map[string]string
|
||||||
return
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
}
|
}
|
||||||
headers := geminiCustomHeaders(auth)
|
util.ApplyCustomHeadersFromAttrs(req, attrs)
|
||||||
if len(headers) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for k, v := range headers {
|
|
||||||
if k == "" || v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
req.Header.Set(k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func geminiCustomHeaders(auth *cliproxyauth.Auth) map[string]string {
|
|
||||||
if auth == nil || auth.Attributes == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
headers := make(map[string]string, len(auth.Attributes))
|
|
||||||
for k, v := range auth.Attributes {
|
|
||||||
if !strings.HasPrefix(k, "header:") {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
name := strings.TrimSpace(strings.TrimPrefix(k, "header:"))
|
|
||||||
if name == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
val := strings.TrimSpace(v)
|
|
||||||
if val == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
headers[name] = val
|
|
||||||
}
|
|
||||||
if len(headers) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return headers
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func fixGeminiImageAspectRatio(modelName string, rawJSON []byte) []byte {
|
func fixGeminiImageAspectRatio(modelName string, rawJSON []byte) []byte {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/config"
|
||||||
|
"github.com/router-for-me/CLIProxyAPI/v6/internal/util"
|
||||||
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
cliproxyauth "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth"
|
||||||
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
cliproxyexecutor "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/executor"
|
||||||
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
sdktranslator "github.com/router-for-me/CLIProxyAPI/v6/sdk/translator"
|
||||||
@@ -66,6 +67,11 @@ func (e *OpenAICompatExecutor) Execute(ctx context.Context, auth *cliproxyauth.A
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
||||||
|
var attrs map[string]string
|
||||||
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
|
}
|
||||||
|
util.ApplyCustomHeadersFromAttrs(httpReq, attrs)
|
||||||
var authID, authLabel, authType, authValue string
|
var authID, authLabel, authType, authValue string
|
||||||
if auth != nil {
|
if auth != nil {
|
||||||
authID = auth.ID
|
authID = auth.ID
|
||||||
@@ -143,6 +149,11 @@ func (e *OpenAICompatExecutor) ExecuteStream(ctx context.Context, auth *cliproxy
|
|||||||
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
httpReq.Header.Set("Authorization", "Bearer "+apiKey)
|
||||||
}
|
}
|
||||||
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
httpReq.Header.Set("User-Agent", "cli-proxy-openai-compat")
|
||||||
|
var attrs map[string]string
|
||||||
|
if auth != nil {
|
||||||
|
attrs = auth.Attributes
|
||||||
|
}
|
||||||
|
util.ApplyCustomHeadersFromAttrs(httpReq, attrs)
|
||||||
httpReq.Header.Set("Accept", "text/event-stream")
|
httpReq.Header.Set("Accept", "text/event-stream")
|
||||||
httpReq.Header.Set("Cache-Control", "no-cache")
|
httpReq.Header.Set("Cache-Control", "no-cache")
|
||||||
var authID, authLabel, authType, authValue string
|
var authID, authLabel, authType, authValue string
|
||||||
|
|||||||
52
internal/util/header_helpers.go
Normal file
52
internal/util/header_helpers.go
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplyCustomHeadersFromAttrs applies user-defined headers stored in the provided attributes map.
|
||||||
|
// Custom headers override built-in defaults when conflicts occur.
|
||||||
|
func ApplyCustomHeadersFromAttrs(r *http.Request, attrs map[string]string) {
|
||||||
|
if r == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
applyCustomHeaders(r, extractCustomHeaders(attrs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractCustomHeaders(attrs map[string]string) map[string]string {
|
||||||
|
if len(attrs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
headers := make(map[string]string)
|
||||||
|
for k, v := range attrs {
|
||||||
|
if !strings.HasPrefix(k, "header:") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
name := strings.TrimSpace(strings.TrimPrefix(k, "header:"))
|
||||||
|
if name == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := strings.TrimSpace(v)
|
||||||
|
if val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
headers[name] = val
|
||||||
|
}
|
||||||
|
if len(headers) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyCustomHeaders(r *http.Request, headers map[string]string) {
|
||||||
|
if r == nil || len(headers) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for k, v := range headers {
|
||||||
|
if k == "" || v == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
r.Header.Set(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -762,16 +762,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if base != "" {
|
if base != "" {
|
||||||
attrs["base_url"] = base
|
attrs["base_url"] = base
|
||||||
}
|
}
|
||||||
if len(entry.Headers) > 0 {
|
addConfigHeadersToAttrs(entry.Headers, attrs)
|
||||||
for hk, hv := range entry.Headers {
|
|
||||||
key := strings.TrimSpace(hk)
|
|
||||||
val := strings.TrimSpace(hv)
|
|
||||||
if key == "" || val == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
attrs["header:"+key] = val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: "gemini",
|
Provider: "gemini",
|
||||||
@@ -803,6 +794,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if hash := computeClaudeModelsHash(ck.Models); hash != "" {
|
if hash := computeClaudeModelsHash(ck.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
|
addConfigHeadersToAttrs(ck.Headers, attrs)
|
||||||
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
@@ -831,6 +823,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if ck.BaseURL != "" {
|
if ck.BaseURL != "" {
|
||||||
attrs["base_url"] = ck.BaseURL
|
attrs["base_url"] = ck.BaseURL
|
||||||
}
|
}
|
||||||
|
addConfigHeadersToAttrs(ck.Headers, attrs)
|
||||||
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
proxyURL := strings.TrimSpace(ck.ProxyURL)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
@@ -873,6 +866,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
|
addConfigHeadersToAttrs(compat.Headers, attrs)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: providerName,
|
Provider: providerName,
|
||||||
@@ -905,6 +899,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
|
addConfigHeadersToAttrs(compat.Headers, attrs)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: providerName,
|
Provider: providerName,
|
||||||
@@ -930,6 +925,7 @@ func (w *Watcher) SnapshotCoreAuths() []*coreauth.Auth {
|
|||||||
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
if hash := computeOpenAICompatModelsHash(compat.Models); hash != "" {
|
||||||
attrs["models_hash"] = hash
|
attrs["models_hash"] = hash
|
||||||
}
|
}
|
||||||
|
addConfigHeadersToAttrs(compat.Headers, attrs)
|
||||||
a := &coreauth.Auth{
|
a := &coreauth.Auth{
|
||||||
ID: id,
|
ID: id,
|
||||||
Provider: providerName,
|
Provider: providerName,
|
||||||
@@ -1131,13 +1127,16 @@ func describeOpenAICompatibilityUpdate(oldEntry, newEntry config.OpenAICompatibi
|
|||||||
newKeyCount := countAPIKeys(newEntry)
|
newKeyCount := countAPIKeys(newEntry)
|
||||||
oldModelCount := countOpenAIModels(oldEntry.Models)
|
oldModelCount := countOpenAIModels(oldEntry.Models)
|
||||||
newModelCount := countOpenAIModels(newEntry.Models)
|
newModelCount := countOpenAIModels(newEntry.Models)
|
||||||
details := make([]string, 0, 2)
|
details := make([]string, 0, 3)
|
||||||
if oldKeyCount != newKeyCount {
|
if oldKeyCount != newKeyCount {
|
||||||
details = append(details, fmt.Sprintf("api-keys %d -> %d", oldKeyCount, newKeyCount))
|
details = append(details, fmt.Sprintf("api-keys %d -> %d", oldKeyCount, newKeyCount))
|
||||||
}
|
}
|
||||||
if oldModelCount != newModelCount {
|
if oldModelCount != newModelCount {
|
||||||
details = append(details, fmt.Sprintf("models %d -> %d", oldModelCount, newModelCount))
|
details = append(details, fmt.Sprintf("models %d -> %d", oldModelCount, newModelCount))
|
||||||
}
|
}
|
||||||
|
if !equalStringMap(oldEntry.Headers, newEntry.Headers) {
|
||||||
|
details = append(details, "headers updated")
|
||||||
|
}
|
||||||
if len(details) == 0 {
|
if len(details) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -1303,6 +1302,9 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) {
|
if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) {
|
||||||
changes = append(changes, fmt.Sprintf("claude[%d].api-key: updated", i))
|
changes = append(changes, fmt.Sprintf("claude[%d].api-key: updated", i))
|
||||||
}
|
}
|
||||||
|
if !equalStringMap(o.Headers, n.Headers) {
|
||||||
|
changes = append(changes, fmt.Sprintf("claude[%d].headers: updated", i))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1325,6 +1327,9 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) {
|
if strings.TrimSpace(o.APIKey) != strings.TrimSpace(n.APIKey) {
|
||||||
changes = append(changes, fmt.Sprintf("codex[%d].api-key: updated", i))
|
changes = append(changes, fmt.Sprintf("codex[%d].api-key: updated", i))
|
||||||
}
|
}
|
||||||
|
if !equalStringMap(o.Headers, n.Headers) {
|
||||||
|
changes = append(changes, fmt.Sprintf("codex[%d].headers: updated", i))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1357,6 +1362,20 @@ func buildConfigChangeDetails(oldCfg, newCfg *config.Config) []string {
|
|||||||
return changes
|
return changes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func addConfigHeadersToAttrs(headers map[string]string, attrs map[string]string) {
|
||||||
|
if len(headers) == 0 || attrs == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for hk, hv := range headers {
|
||||||
|
key := strings.TrimSpace(hk)
|
||||||
|
val := strings.TrimSpace(hv)
|
||||||
|
if key == "" || val == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
attrs["header:"+key] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func trimStrings(in []string) []string {
|
func trimStrings(in []string) []string {
|
||||||
out := make([]string, len(in))
|
out := make([]string, len(in))
|
||||||
for i := range in {
|
for i := range in {
|
||||||
|
|||||||
Reference in New Issue
Block a user