diff --git a/cmd/llmsproxy/main.go b/cmd/llmsproxy/main.go index a0d9b56..13c43d4 100644 --- a/cmd/llmsproxy/main.go +++ b/cmd/llmsproxy/main.go @@ -88,4 +88,4 @@ func main() { signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM) <-stop log.Printf("[llmsproxy] shutting down") -} \ No newline at end of file +} diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 652dcd1..ba38d9b 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -326,4 +326,3 @@ func statusOf(resp *http.Response) int { } return resp.StatusCode } - diff --git a/internal/config/config.go b/internal/config/config.go index f0bde6f..87be1c7 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -24,9 +24,9 @@ type Config struct { TLSKeyFile string `yaml:"tls_key_file,omitempty"` // PEM private key PublicBaseURL string `yaml:"public_base_url,omitempty"` // external base for generated config snippets; default inferred from request Sources []Source `yaml:"sources"` - Auto []ModelScope `yaml:"auto,omitempty"` // AUTO 调度链规则(WebUI 优先级页编辑,chat) - AutoImage []ModelScope `yaml:"auto_image,omitempty"` // AUTO 生图调度链规则(WebUI 优先级页·生图) - Keys []GWKey `yaml:"keys,omitempty"` // 网关密钥(WebUI 密钥页管理) + Auto []ModelScope `yaml:"auto,omitempty"` // AUTO 调度链规则(WebUI 优先级页编辑,chat) + AutoImage []ModelScope `yaml:"auto_image,omitempty"` // AUTO 生图调度链规则(WebUI 优先级页·生图) + Keys []GWKey `yaml:"keys,omitempty"` // 网关密钥(WebUI 密钥页管理) } // Defaults applied to any source (YAML or runtime) that leaves a field unset. @@ -314,32 +314,32 @@ func (c *Config) ApplyDefaults() error { // RuntimeConfig is the legacy runtime file format (kept for migration only). type RuntimeConfig struct { - Sources []Source `json:"sources,omitempty"` - SourceTemplates []SourceTemplate `json:"source_templates,omitempty"` - DeletedSources []string `json:"deleted_sources,omitempty"` - DeletedAdapters []string `json:"deleted_adapters,omitempty"` - PresetTemplates []string `json:"preset_templates,omitempty"` // preset names the user has seen (or deleted) — never re-seeded - Keys []GWKey `json:"keys,omitempty"` - Auto []ModelScope `json:"auto,omitempty"` - AutoImage []ModelScope `json:"auto_image,omitempty"` + Sources []Source `json:"sources,omitempty"` + SourceTemplates []SourceTemplate `json:"source_templates,omitempty"` + DeletedSources []string `json:"deleted_sources,omitempty"` + DeletedAdapters []string `json:"deleted_adapters,omitempty"` + PresetTemplates []string `json:"preset_templates,omitempty"` // preset names the user has seen (or deleted) — never re-seeded + Keys []GWKey `json:"keys,omitempty"` + Auto []ModelScope `json:"auto,omitempty"` + AutoImage []ModelScope `json:"auto_image,omitempty"` } // SourceTemplate stores reusable source configuration (everything except // name and api_key) so the WebUI can spin up multiple key-bearing sources // from one shared template. type SourceTemplate struct { - Name string `json:"name"` - BaseURL string `json:"base_url"` - Adapter string `json:"adapter"` - Endpoint string `json:"endpoint,omitempty"` - ImageEndpoint string `json:"image_endpoint,omitempty"` - Models []Model `json:"models"` - Headers map[string]string `json:"headers,omitempty"` - Meta map[string]interface{} `json:"meta,omitempty"` - Temperature float64 `json:"temperature,omitempty"` - MaxTokens int `json:"max_tokens,omitempty"` - MaxConcurrent int `json:"max_concurrent,omitempty"` - RPM int `json:"rpm,omitempty"` + Name string `json:"name"` + BaseURL string `json:"base_url"` + Adapter string `json:"adapter"` + Endpoint string `json:"endpoint,omitempty"` + ImageEndpoint string `json:"image_endpoint,omitempty"` + Models []Model `json:"models"` + Headers map[string]string `json:"headers,omitempty"` + Meta map[string]interface{} `json:"meta,omitempty"` + Temperature float64 `json:"temperature,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + MaxConcurrent int `json:"max_concurrent,omitempty"` + RPM int `json:"rpm,omitempty"` } // GWKey is a gateway API key persisted in the config file. Role is "admin" diff --git a/internal/gateway/chat.go b/internal/gateway/chat.go index 01f7dba..3fd6887 100644 --- a/internal/gateway/chat.go +++ b/internal/gateway/chat.go @@ -54,11 +54,11 @@ type RespMessage struct { } type ChatChunk struct { - ID string `json:"id"` - Object string `json:"object"` - Created int64 `json:"created"` - Model string `json:"model"` - Choices []ChunkChoice `json:"choices"` + ID string `json:"id"` + Object string `json:"object"` + Created int64 `json:"created"` + Model string `json:"model"` + Choices []ChunkChoice `json:"choices"` // Usage is sent in the final chunk of a stream (empty choices) so // OpenAI-compatible clients can read token usage. Usage *types.TokenUsage `json:"usage,omitempty"` diff --git a/internal/gateway/keys.go b/internal/gateway/keys.go index 2d8c1e7..70445c7 100644 --- a/internal/gateway/keys.go +++ b/internal/gateway/keys.go @@ -35,10 +35,10 @@ func (g *Gateway) handleKeysAPI(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]interface{}{"keys": g.core.ListKeys()}) case http.MethodPost: var body struct { - Name string `json:"name"` - Role string `json:"role"` - Models []config.ModelScope `json:"models"` - Note string `json:"note"` + Name string `json:"name"` + Role string `json:"role"` + Models []config.ModelScope `json:"models"` + Note string `json:"note"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "invalid_request", "invalid json: "+err.Error()) @@ -183,4 +183,4 @@ func (g *Gateway) handleAutoAPI(w http.ResponseWriter, r *http.Request) { default: writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "") } -} \ No newline at end of file +} diff --git a/internal/gateway/login_guard_test.go b/internal/gateway/login_guard_test.go index 44a3843..2150155 100644 --- a/internal/gateway/login_guard_test.go +++ b/internal/gateway/login_guard_test.go @@ -59,7 +59,9 @@ func TestLoginBruteForceLockout(t *testing.T) { func TestLoginSuccessResetsFailures(t *testing.T) { g := newTestGateway(t) - mkBad := func() *http.Request { return httptest.NewRequest("POST", "/api/login", strings.NewReader(`{"key":"nope"}`)) } + mkBad := func() *http.Request { + return httptest.NewRequest("POST", "/api/login", strings.NewReader(`{"key":"nope"}`)) + } for i := 0; i < loginMaxFails-1; i++ { // one below the lockout threshold rr := httptest.NewRecorder() g.handleLoginAPI(rr, mkBad()) diff --git a/internal/types/types.go b/internal/types/types.go index 7e49c5f..c6fc550 100644 --- a/internal/types/types.go +++ b/internal/types/types.go @@ -19,15 +19,15 @@ var ErrBusy = errors.New("provider busy") // ---- OpenAI wire request (gateway input) ---- type ChatRequest struct { - Model string `json:"model"` - Messages []ChatMessage `json:"messages"` - Temperature *float64 `json:"temperature,omitempty"` - MaxTokens int `json:"max_tokens,omitempty"` - Stream bool `json:"stream,omitempty"` - Tools []interface{} `json:"tools,omitempty"` - ToolChoice interface{} `json:"tool_choice,omitempty"` - DisableThinking bool `json:"disable_thinking"` - ExtraBody map[string]interface{} `json:"-"` + Model string `json:"model"` + Messages []ChatMessage `json:"messages"` + Temperature *float64 `json:"temperature,omitempty"` + MaxTokens int `json:"max_tokens,omitempty"` + Stream bool `json:"stream,omitempty"` + Tools []interface{} `json:"tools,omitempty"` + ToolChoice interface{} `json:"tool_choice,omitempty"` + DisableThinking bool `json:"disable_thinking"` + ExtraBody map[string]interface{} `json:"-"` } func (r *ChatRequest) MarshalJSON() ([]byte, error) { @@ -119,23 +119,23 @@ func (t TokenUsage) MarshalJSON() ([]byte, error) { pdetails = &PromptTokensDetails{CachedTokens: t.PromptCacheHit} } return json.Marshal(struct { - PromptTokens int `json:"prompt_tokens"` - CompletionTokens int `json:"completion_tokens"` - TotalTokens int `json:"total_tokens"` - Prompt int `json:"prompt"` - Completion int `json:"completion"` - Total int `json:"total"` - PromptTokensDetails *PromptTokensDetails `json:"prompt_tokens_details,omitempty"` - PromptCacheHitTokens int `json:"prompt_cache_hit_tokens,omitempty"` - PromptCacheMissTokens int `json:"prompt_cache_miss_tokens,omitempty"` + PromptTokens int `json:"prompt_tokens"` + CompletionTokens int `json:"completion_tokens"` + TotalTokens int `json:"total_tokens"` + Prompt int `json:"prompt"` + Completion int `json:"completion"` + Total int `json:"total"` + PromptTokensDetails *PromptTokensDetails `json:"prompt_tokens_details,omitempty"` + PromptCacheHitTokens int `json:"prompt_cache_hit_tokens,omitempty"` + PromptCacheMissTokens int `json:"prompt_cache_miss_tokens,omitempty"` }{ - PromptTokens: t.Prompt, - CompletionTokens: t.Completion, - TotalTokens: t.Total, - Prompt: t.Prompt, - Completion: t.Completion, - Total: t.Total, - PromptTokensDetails: pdetails, + PromptTokens: t.Prompt, + CompletionTokens: t.Completion, + TotalTokens: t.Total, + Prompt: t.Prompt, + Completion: t.Completion, + Total: t.Total, + PromptTokensDetails: pdetails, PromptCacheHitTokens: t.PromptCacheHit, PromptCacheMissTokens: t.PromptCacheMiss, }) @@ -168,8 +168,8 @@ type ImageGenResponse struct { // streaming tool_calls array (incremental fragments with an index field), which // OpenAI-compatible clients accumulate themselves. type UnifiedChunk struct { - Content string `json:"content"` - Done bool `json:"done"` + Content string `json:"content"` + Done bool `json:"done"` // FinishReason carries the upstream finish/stop reason ("tool_calls", // "length", ...) when the adapter provides it; the gateway emits it on // the terminating chunk instead of the default "stop". @@ -204,4 +204,4 @@ func OneLine(s string, n int) string { s = s[:n] + "..." } return s -} \ No newline at end of file +}