diff --git a/.github/required-checks.txt b/.github/required-checks.txt index c9cbf6eab7..31c0885749 100644 --- a/.github/required-checks.txt +++ b/.github/required-checks.txt @@ -1,16 +1,5 @@ # workflow_file|job_name -pr-test-build.yml|go-ci -pr-test-build.yml|quality-ci -pr-test-build.yml|quality-staged-check -pr-test-build.yml|fmt-check -pr-test-build.yml|golangci-lint -pr-test-build.yml|route-lifecycle -pr-test-build.yml|provider-smoke-matrix -pr-test-build.yml|provider-smoke-matrix-cheapest -pr-test-build.yml|test-smoke -pr-test-build.yml|pre-release-config-compat-smoke -pr-test-build.yml|distributed-critical-paths -pr-test-build.yml|changelog-scope-classifier -pr-test-build.yml|docs-build -pr-test-build.yml|ci-summary +pr-test-build.yml|build pr-path-guard.yml|ensure-no-translator-changes +required-check-names-guard.yml|verify-required-check-names +codeql.yml|Analyze (Go) diff --git a/.github/workflows/auto-merge.yml b/.github/workflows/auto-merge.yml new file mode 100644 index 0000000000..008dd16f7c --- /dev/null +++ b/.github/workflows/auto-merge.yml @@ -0,0 +1,33 @@ +name: Auto Merge Gate + +on: + pull_request_target: + types: + - opened + - reopened + - ready_for_review + - synchronize + - labeled + pull_request_review: + types: + - submitted + +permissions: + contents: read + pull-requests: write + +jobs: + enable-automerge: + if: | + (github.event_name != 'pull_request_review') || + (github.event.review.state == 'APPROVED') + runs-on: ubuntu-latest + steps: + - name: Enable auto-merge for labeled PRs + if: | + contains(github.event.pull_request.labels.*.name, 'automerge') && + !contains(github.event.pull_request.labels.*.name, 'do-not-merge') + uses: peter-evans/enable-pull-request-automerge@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + merge-method: squash diff --git a/.github/workflows/pr-path-guard.yml b/.github/workflows/pr-path-guard.yml index 4fe3d93881..4a99fc4acd 100644 --- a/.github/workflows/pr-path-guard.yml +++ b/.github/workflows/pr-path-guard.yml @@ -9,6 +9,7 @@ on: jobs: ensure-no-translator-changes: + name: ensure-no-translator-changes runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/pr-test-build.yml b/.github/workflows/pr-test-build.yml index 477ff0498e..2fe1994b84 100644 --- a/.github/workflows/pr-test-build.yml +++ b/.github/workflows/pr-test-build.yml @@ -8,6 +8,7 @@ permissions: jobs: build: + name: build runs-on: ubuntu-latest steps: - name: Checkout diff --git a/.worktrees/config/m/config-build/active/pkg/llmproxy/config/sdk_types.go b/.worktrees/config/m/config-build/active/pkg/llmproxy/config/sdk_types.go index bf4fb90ecf..834d2aba6e 100644 --- a/.worktrees/config/m/config-build/active/pkg/llmproxy/config/sdk_types.go +++ b/.worktrees/config/m/config-build/active/pkg/llmproxy/config/sdk_types.go @@ -1,43 +1,8 @@ -// Package config provides configuration types for CLI Proxy API. -// This file contains SDK-specific config types that are used by internal/* packages. +// Package config provides configuration types for the llmproxy server. package config -// SDKConfig represents the SDK-level configuration embedded in Config. -type SDKConfig struct { - // ProxyURL is the URL of an optional proxy server to use for outbound requests. - ProxyURL string `yaml:"proxy-url" json:"proxy-url"` +import sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" - // ForceModelPrefix requires explicit model prefixes (e.g., "teamA/gemini-3-pro-preview") - // to target prefixed credentials. When false, unprefixed model requests may use prefixed - // credentials as well. - ForceModelPrefix bool `yaml:"force-model-prefix" json:"force-model-prefix"` - - // RequestLog enables or disables detailed request logging functionality. - RequestLog bool `yaml:"request-log" json:"request-log"` - - // APIKeys is a list of keys for authenticating clients to this proxy server. - APIKeys []string `yaml:"api-keys" json:"api-keys"` - - // PassthroughHeaders controls whether upstream response headers are forwarded to downstream clients. - // Default is false (disabled). - PassthroughHeaders bool `yaml:"passthrough-headers" json:"passthrough-headers"` - - // Streaming configures server-side streaming behavior (keep-alives and safe bootstrap retries). - Streaming StreamingConfig `yaml:"streaming" json:"streaming"` - - // NonStreamKeepAliveInterval controls how often blank lines are emitted for non-streaming responses. - // <= 0 disables keep-alives. Value is in seconds. - NonStreamKeepAliveInterval int `yaml:"nonstream-keepalive-interval,omitempty" json:"nonstream-keepalive-interval,omitempty"` -} - -// StreamingConfig holds server streaming behavior configuration. -type StreamingConfig struct { - // KeepAliveSeconds controls how often the server emits SSE heartbeats (": keep-alive\n\n"). - // <= 0 disables keep-alives. Default is 0. - KeepAliveSeconds int `yaml:"keepalive-seconds,omitempty" json:"keepalive-seconds,omitempty"` - - // BootstrapRetries controls how many times the server may retry a streaming request before any bytes are sent, - // to allow auth rotation / transient recovery. - // <= 0 disables bootstrap retries. Default is 0. - BootstrapRetries int `yaml:"bootstrap-retries,omitempty" json:"bootstrap-retries,omitempty"` -} +// Keep SDK types aligned with public SDK config to avoid split-type regressions. +type SDKConfig = sdkconfig.SDKConfig +type StreamingConfig = sdkconfig.StreamingConfig diff --git a/internal/config/config.go b/internal/config/config.go index e2a09ef720..421d473db5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -89,6 +89,10 @@ type Config struct { // Nil means enabled (default behavior). ResponsesWebsocketEnabled *bool `yaml:"responses-websocket-enabled,omitempty" json:"responses-websocket-enabled,omitempty"` + // ResponsesCompactEnabled gates the /v1/responses/compact route rollout. + // Nil means enabled (default behavior). + ResponsesCompactEnabled *bool `yaml:"responses-compact-enabled,omitempty" json:"responses-compact-enabled,omitempty"` + // GeminiKey defines Gemini API key configurations with optional routing overrides. GeminiKey []GeminiKey `yaml:"gemini-api-key" json:"gemini-api-key"` diff --git a/internal/config/responses_compact_toggle.go b/internal/config/responses_compact_toggle.go new file mode 100644 index 0000000000..8295da8b2c --- /dev/null +++ b/internal/config/responses_compact_toggle.go @@ -0,0 +1,11 @@ +package config + +// IsResponsesCompactEnabled reports whether /v1/responses/compact is enabled. +// Default is true when config or toggle is unset. +func (c *Config) IsResponsesCompactEnabled() bool { + if c == nil || c.ResponsesCompactEnabled == nil { + return true + } + return *c.ResponsesCompactEnabled +} + diff --git a/pkg/llmproxy/access/reconcile.go b/pkg/llmproxy/access/reconcile.go index 72766ff6ce..9ba5193a3a 100644 --- a/pkg/llmproxy/access/reconcile.go +++ b/pkg/llmproxy/access/reconcile.go @@ -85,7 +85,7 @@ func ApplyAccessProviders(manager *sdkaccess.Manager, oldCfg, newCfg *config.Con } existing := manager.Providers() - configaccess.Register((*config.SDKConfig)(&newCfg.SDKConfig)) + configaccess.Register(&newCfg.SDKConfig) providers, added, updated, removed, err := ReconcileProviders(oldCfg, newCfg, existing) if err != nil { log.Errorf("failed to reconcile request auth providers: %v", err) diff --git a/pkg/llmproxy/api/aliases.go b/pkg/llmproxy/api/aliases.go index 7ba458d7d6..da854afa84 100644 --- a/pkg/llmproxy/api/aliases.go +++ b/pkg/llmproxy/api/aliases.go @@ -14,6 +14,7 @@ var ( WithEngineConfigurator = api.WithEngineConfigurator WithLocalManagementPassword = api.WithLocalManagementPassword WithKeepAliveEndpoint = api.WithKeepAliveEndpoint + WithPostAuthHook = api.WithPostAuthHook WithRequestLoggerFactory = api.WithRequestLoggerFactory NewServer = api.NewServer ) diff --git a/pkg/llmproxy/api/handlers/management/config_basic.go b/pkg/llmproxy/api/handlers/management/config_basic.go index 8039d856b9..038b67977f 100644 --- a/pkg/llmproxy/api/handlers/management/config_basic.go +++ b/pkg/llmproxy/api/handlers/management/config_basic.go @@ -12,7 +12,6 @@ import ( "github.com/gin-gonic/gin" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/util" - sdkconfig "github.com/router-for-me/CLIProxyAPI/v6/sdk/config" log "github.com/sirupsen/logrus" "gopkg.in/yaml.v3" ) @@ -45,7 +44,7 @@ func (h *Handler) GetLatestVersion(c *gin.Context) { proxyURL = strings.TrimSpace(h.cfg.ProxyURL) } if proxyURL != "" { - sdkCfg := &sdkconfig.SDKConfig{ProxyURL: proxyURL} + sdkCfg := &config.SDKConfig{ProxyURL: proxyURL} util.SetProxy(sdkCfg, client) } diff --git a/pkg/llmproxy/config/sdk_config.go b/pkg/llmproxy/config/sdk_config.go index 63e25a079b..4156fb954b 100644 --- a/pkg/llmproxy/config/sdk_config.go +++ b/pkg/llmproxy/config/sdk_config.go @@ -6,8 +6,16 @@ package config import internalconfig "github.com/router-for-me/CLIProxyAPI/v6/internal/config" +// Config is an alias to internal/config.Config. +type Config = internalconfig.Config + // SDKConfig is an alias to internal/config.SDKConfig. type SDKConfig = internalconfig.SDKConfig // StreamingConfig is an alias to internal/config.StreamingConfig. type StreamingConfig = internalconfig.StreamingConfig + +var ( + LoadConfig = internalconfig.LoadConfig + SaveConfigPreserveComments = internalconfig.SaveConfigPreserveComments +) diff --git a/sdk/api/options.go b/sdk/api/options.go index 5149fb51b0..1880635884 100644 --- a/sdk/api/options.go +++ b/sdk/api/options.go @@ -9,6 +9,7 @@ import ( "github.com/gin-gonic/gin" internalapi "github.com/router-for-me/CLIProxyAPI/v6/internal/api" + "github.com/router-for-me/CLIProxyAPI/v6/sdk/cliproxy/auth" "github.com/router-for-me/CLIProxyAPI/v6/sdk/api/handlers" "github.com/router-for-me/CLIProxyAPI/v6/internal/config" "github.com/router-for-me/CLIProxyAPI/v6/internal/logging" @@ -44,3 +45,8 @@ func WithKeepAliveEndpoint(timeout time.Duration, onTimeout func()) ServerOption func WithRequestLoggerFactory(factory func(*config.Config, string) logging.RequestLogger) ServerOption { return internalapi.WithRequestLoggerFactory(factory) } + +// WithPostAuthHook registers a hook to be called after auth record creation. +func WithPostAuthHook(hook auth.PostAuthHook) ServerOption { + return internalapi.WithPostAuthHook(hook) +} diff --git a/sdk/auth/codex.go b/sdk/auth/codex.go index 83bb49667e..cdf99182fa 100644 --- a/sdk/auth/codex.go +++ b/sdk/auth/codex.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/auth/codex" + "github.com/router-for-me/CLIProxyAPI/v6/internal/auth/codex" "github.com/router-for-me/CLIProxyAPI/v6/pkg/llmproxy/browser" // legacy client removed "github.com/router-for-me/CLIProxyAPI/v6/internal/config"