Documentation
¶
Overview ¶
Package oauthproxy implements ccl's local subscription and protocol runtimes.
Production traffic for openai / openai_responses / OAuth providers goes through this package only. Claude Code talks to a loopback Anthropic Messages endpoint. Most providers use embedded CLIProxyAPI; Kiro uses ccl's direct Messages-to-Amazon-Q adapter and AWS EventStream decoder; Qoder uses ccl's direct OAuth/COSY/SSE adapter and never invokes Qoder CLI.
Compatibility boundary with CLIProxyAPI ¶
Several behaviors below are deliberate workarounds for SDK gaps. Treat them as a regression checklist whenever the pinned github.com/router-for-me/CLIProxyAPI/v7 version changes:
responsesCompatibilityProxy (responses_compat.go) Placed in front of every Responses upstream (plain and Codex). It: (a) rewrites completed-only streams into a normal output_text.delta because CLIProxyAPI's streaming Claude translator currently ignores text that only appears in response.completed; (b) ensures response.created precedes any content event and drops a late real created after a synthetic one, so the translator never emits content before message_start or a second message_start; and (c) for plain Responses, strips residual Codex headers/body that the SDK's codex-api-key executor always injects (codex-tui UA, Session_id, client_metadata, Originator) and replaces UA with ccl-openai-responses. Dedicated Codex bases still inject full Codex client identity. Remove or shrink once the SDK exposes a non-Codex Responses executor.
Runtime.Stop shutdown ordering (runtime.go) Service.Run performs its own deferred Shutdown after the run context is canceled. Calling Shutdown concurrently with that final path races inside CLIProxyAPI, so Stop waits up to 5s for Run to exit and only force-calls Shutdown on timeout. Keep that order when changing teardown.
Log / stdout isolation (silenceSDKLogs, silenceStdout) CLIProxyAPI uses logrus and may write startup noise to stdout. ccl temporarily silences both while the embedded service becomes ready, and keeps logrus discarded after the last runtime stops because refresh workers can still log after Shutdown. Nested starts use reference counts.
Session credentials All runtimes bind 127.0.0.1 only and use a random per-session API key that is never written back to ~/.ccl/config.yaml. OAuth credentials live under ~/.ccl/auth and are filtered per backend so multi-login providers do not share models or refresh tokens.
Model registration cleanup Stop unregisters every auth ID from cliproxy.GlobalModelRegistry so a later provider does not inherit another backend's routes.
CCL cooldown override (codex_cooldown.go) GPT OAuth and ordinary API-key runtimes shorten 408/5xx failures to 2s and 401/429 failures to 10s. The result hook updates the SDK manager after MarkResult and clears the SDK registry's longer 401/429 side effects. Kiro has an independent direct adapter and does not use this policy.
GitHub Copilot direct gateway (copilot_runtime.go) Copilot does not use CLIProxyAPI OAuth credentials. ccl authenticates with GitHub, discovers the account's authoritative model catalog, and routes each model to its advertised Chat, Responses, or Messages endpoint before the local compatibility layer. Do not add synthetic request identity headers without testing the real Copilot API: they can change model visibility or entitlement decisions.
Qoder direct runtime (qoder_*.go) Qoder browser OAuth, refresh, COSY signing, WAF body encoding, model discovery, and Anthropic Messages translation all run in this process. The upstream request's session_type="qodercli" is a protocol identity field only; do not replace the direct runtime with a qodercli subprocess.
When upgrading CLIProxyAPI, run at least:
go test ./internal/oauthproxy ./internal/claude ./cmd
and manually exercise ccl oauth gpt, ccl oauth copilot, ccl oauth qoder, ccl oauth kiro, an openai_responses API-key provider, and a plain openai(chat) provider with streaming + tool calls.
Note: dedicated Codex bases still set Originator to embeddedCodexOriginator ("codex_cli_rs") for custom API-key Codex endpoints. That is independent of CLIProxyAPI's default codex-tui User-Agent for OAuth/SDK-managed requests.
Index ¶
- Constants
- func AuthDir() (string, error)
- func BackendProvider(providerName string) (string, error)
- func CloseLog()
- func ConfigureLogLevel(level LogLevel)
- func DebugHTTPBody(label string, body []byte)
- func EnsureSessionLog(prefix string) (path string, owned bool, err error)
- func FormatUsageSummary(totals []UsageModelTotals) string
- func LogConfigured() bool
- func LogDebugEnabled() bool
- func LogDebugf(format string, args ...any)
- func LogDir() (string, error)
- func LogEnabled() bool
- func LogErrorf(format string, args ...any)
- func LogFilePath() string
- func LogInfof(format string, args ...any)
- func LogUpstreamStatusf(status int, format string, args ...any)
- func LogWarnf(format string, args ...any)
- func ResolveLogTemplatePath() string
- func SessionLogPath(session string) string
- func SetLogLevel(level LogLevel, path string) error
- func ValidateLoginProvider(providerName string) (string, error)
- type CredentialInfo
- type LogLevel
- type LoginOptions
- type LoginResult
- type Runtime
- func Start(parent context.Context, providerName string) (*Runtime, error)
- func StartCodexAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string) (*Runtime, error)
- func StartOAuth(parent context.Context, providerName, modelSpec, credentialFile string) (*Runtime, error)
- func StartOAuthAccounts(parent context.Context, providerName, modelSpec string, ...) (*Runtime, error)
- func StartOpenAIChatAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string) (*Runtime, error)
- func StartOpenAIResponsesAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string, ...) (*Runtime, error)
- func StartProvider(parent context.Context, options StartOptions) (*Runtime, error)
- func (r *Runtime) APIKey() string
- func (r *Runtime) ClaudeBaseURL() string
- func (r *Runtime) Endpoint() string
- func (r *Runtime) ListAuths() []*coreauth.Auth
- func (r *Runtime) ModelDisplayNames() map[string]string
- func (r *Runtime) Models() []string
- func (r *Runtime) Stop()
- func (r *Runtime) Usage() *UsageTracker
- type StartOptions
- type UpstreamProtocol
- type UsageModelTotals
- type UsageTotals
- type UsageTracker
Constants ¶
const ( ProviderCodex = "codex" ProviderGemini = "gemini" ProviderChatGPT = "gpt" // ProviderChatGPTLegacy is accepted by auth for older configs/docs. ProviderChatGPTLegacy = "chatgpt" ProviderGrok = "grok" ProviderCopilot = "copilot" ProviderQoder = "qoder" ProviderKimi = "kimi" ProviderKiro = "kiro" ProviderClaude = "claude" )
const ( KiroAuthModePortal = "portal" KiroAuthModeBuilderID = "builder" )
Variables ¶
This section is empty.
Functions ¶
func BackendProvider ¶
func CloseLog ¶ added in v1.4.0
func CloseLog()
CloseLog closes the active session file while preserving the configured threshold for the next session.
func ConfigureLogLevel ¶ added in v1.4.0
func ConfigureLogLevel(level LogLevel)
ConfigureLogLevel records the logging threshold without creating a shared file. A Claude session or temporary provider runtime opens its own sink when it starts.
func DebugHTTPBody ¶ added in v1.4.0
DebugHTTPBody writes an explicitly debug-level HTTP payload. Callers must never pass headers because they can contain credentials.
func EnsureSessionLog ¶ added in v1.4.0
EnsureSessionLog opens a uniquely named file for a temporary runtime when a caller has not already opened the surrounding Claude session's file. owned tells the runtime whether it must close the sink during teardown.
func FormatUsageSummary ¶ added in v1.4.0
func FormatUsageSummary(totals []UsageModelTotals) string
FormatUsageSummary renders one line per model plus a total line, in the style of the existing "[ccl log] session ended" line: a single fixed prefix, printed unconditionally rather than gated behind the debug toggle, because this is usage information for the user, not a diagnostic.
Models are sorted by total tokens (input+output), largest first, so the model that mattered most for cost is the first thing printed.
func LogConfigured ¶ added in v1.4.0
func LogConfigured() bool
LogConfigured reports whether a session should open a log file.
func LogDebugEnabled ¶ added in v1.4.0
func LogDebugEnabled() bool
LogDebugEnabled reports whether DEBUG entries are collected. HTTP payloads are deliberately DEBUG only because they can contain full prompts, tools, and user-provided secrets.
func LogDebugf ¶ added in v1.4.0
LogDebugf writes sensitive or high-volume detail visible only with `ccl log --level debug`.
func LogEnabled ¶ added in v1.4.0
func LogEnabled() bool
LogEnabled reports whether ccl's current session file is active.
func LogFilePath ¶ added in v1.4.0
func LogFilePath() string
LogFilePath reports the active session log path, or an empty string when off.
func LogInfof ¶ added in v1.4.0
LogInfof writes a normal runtime event. Existing ccl diagnostics use this level so `ccl log on` is useful without exposing request payloads.
func LogUpstreamStatusf ¶ added in v1.4.0
LogUpstreamStatusf classifies HTTP status records consistently. Successful per-request records are DEBUG; client failures are WARN; server failures are ERROR.
func LogWarnf ¶ added in v1.4.0
LogWarnf and LogErrorf are available for callers that can classify an event.
func ResolveLogTemplatePath ¶ added in v1.4.0
func ResolveLogTemplatePath() string
ResolveLogTemplatePath returns the filename template used to derive each suffixed session log. The template itself is never opened by ccl.
func SessionLogPath ¶ added in v1.4.0
SessionLogPath derives one log file per temporary Claude session from the configured base path. Keeping the session name in the filename lets all logger levels write together without interleaving unrelated Claude sessions.
func SetLogLevel ¶ added in v1.4.0
SetLogLevel opens ccl's current per-session log sink. A level of "off" disables logging; all other levels use Go's standard slog text handler. File-system failures are returned instead of silently disabling diagnostics.
func ValidateLoginProvider ¶
ValidateLoginProvider returns the canonical public OAuth provider name. Codex remains an internal backend and a legacy runtime value, but new logins use the public GPT name (model family) because both routes authenticate the same account. Copilot is a separate GitHub OAuth and API backend.
Types ¶
type CredentialInfo ¶ added in v1.3.13
type CredentialInfo struct {
FileName string
Backend string
OAuthProvider string
Email string
Disabled bool
QuotaExceeded bool
Status string
StatusMessage string
}
CredentialInfo is the non-secret identity ccl needs for import, sync, and group selection. FileName is always a basename under ~/.ccl/auth. Disabled / Unavailable / QuotaExceeded reflect CPA-persisted account health when present in the credential JSON (runtime may also keep these in memory only).
func ImportCredential ¶ added in v1.3.13
func ImportCredential(sourcePath, providerHint string) (CredentialInfo, string, error)
ImportCredential validates an existing ccl-supported auth JSON, normalizes its backend type and filename, and stores an independent 0600 copy in ~/.ccl/auth. providerHint can disambiguate legacy aliases, but cannot change one backend into another.
func ListCredentials ¶ added in v1.3.13
func ListCredentials() ([]CredentialInfo, error)
ListCredentials reads supported JSON files directly inside ~/.ccl/auth. Subdirectories are deliberately ignored so import/sync have the same one-level scope.
type LogLevel ¶ added in v1.4.0
type LogLevel string
LogLevel is ccl's persisted representation of the standard slog levels. "off" disables file logging entirely.
func CurrentLogLevel ¶ added in v1.4.0
func CurrentLogLevel() LogLevel
CurrentLogLevel reports the active logging threshold.
func ParseLogLevel ¶ added in v1.4.0
ParseLogLevel accepts ccl's standard logging levels.
type LoginOptions ¶
type LoginResult ¶
func Login ¶
func Login(ctx context.Context, providerName string, opts LoginOptions) (LoginResult, error)
type Runtime ¶
type Runtime struct {
// contains filtered or unexported fields
}
func StartCodexAPI ¶
func StartCodexAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string) (*Runtime, error)
StartCodexAPI starts an embedded CLIProxyAPI runtime for a dedicated Codex Responses endpoint (…/codex). It injects Codex client identity headers and body metadata required by Codex-compatible upstreams.
func StartOAuth ¶ added in v1.3.4
func StartOAuthAccounts ¶ added in v1.3.12
func StartOAuthAccounts(parent context.Context, providerName, modelSpec string, credentialFiles []string) (*Runtime, error)
StartOAuthAccounts starts an OAuth runtime restricted to exactly the supplied canonical credential files. It is the group counterpart to StartOAuth.
func StartOpenAIChatAPI ¶ added in v1.3.4
func StartOpenAIChatAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string) (*Runtime, error)
StartOpenAIChatAPI starts CLIProxyAPI with an OpenAI-compatible Chat Completions upstream. CLIProxyAPI owns both request and response translation.
func StartOpenAIResponsesAPI ¶ added in v1.3.5
func StartOpenAIResponsesAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string, maxOutputTokens int) (*Runtime, error)
StartOpenAIResponsesAPI starts an embedded CLIProxyAPI runtime against a plain OpenAI Responses upstream (not a dedicated Codex base).
CLIProxyAPI only exposes a Responses upstream executor through codex-api-key, so the config still uses that slot. Unlike StartCodexAPI, no Codex Originator / User-Agent / client_metadata / session headers are injected — plain gateways often reject those as unsupported parameters. maxOutputTokens is re-injected by the plain Responses compatibility proxy because CLIProxyAPI currently drops Claude max_tokens on the Codex path.
func StartProvider ¶ added in v1.3.4
func StartProvider(parent context.Context, options StartOptions) (*Runtime, error)
StartProvider starts a loopback Anthropic Messages adapter. Most backends use embedded CLIProxyAPI; Kiro uses the direct Amazon Q adapter in kiro_server.go.
openai_responses is split:
- dedicated Codex bases (…/codex) → StartCodexAPI with Codex client identity
- plain Responses gateways → StartOpenAIResponsesAPI without Codex headers/body
Invalid Codex paths such as …/codex/v1 are rejected before routing so they cannot fall through to the plain Responses path and hit …/codex/v1/responses.
func (*Runtime) ClaudeBaseURL ¶ added in v1.3.4
ClaudeBaseURL is the origin Claude Code uses before appending /v1/messages. Endpoint includes /v1 because ccl's model and diagnostics clients expect an OpenAI API root.
func (*Runtime) ListAuths ¶ added in v1.3.13
ListAuths returns the credentials currently loaded in this runtime, already filtered to the OAuth backend and selected account/group membership.
func (*Runtime) ModelDisplayNames ¶ added in v1.4.0
ModelDisplayNames returns the provider catalog's human-facing labels keyed by technical model ID. Direct adapters may expose these labels as UI aliases when they also resolve each alias back to the ID before the upstream request.
func (*Runtime) Models ¶ added in v1.4.0
Models returns the authoritative upstream catalog captured when the runtime started. It avoids treating compatibility-layer built-ins as provider models.
func (*Runtime) Stop ¶
func (r *Runtime) Stop()
Stop tears down the embedded CLIProxyAPI service.
Teardown order is part of the CLIProxyAPI compatibility boundary (see package doc): cancel the run context, wait for Service.Run to exit on its own, and only force Service.Shutdown if that wait times out. Concurrent Shutdown during Run's deferred cleanup races inside the SDK.
func (*Runtime) Usage ¶ added in v1.4.0
func (r *Runtime) Usage() *UsageTracker
Usage returns the token usage accumulated by this runtime so far. Safe to call at any point in the runtime's lifetime, including after Stop.
type StartOptions ¶ added in v1.3.4
type StartOptions struct {
Protocol UpstreamProtocol
Endpoint string
APIKey string
ModelSpec string
OAuthProvider string
// OAuthAccountCredential optionally restricts the runtime to a single
// credential file (basename under the OAuth auth dir) for this backend.
OAuthAccountCredential string
// OAuthAccountCredentials restricts the runtime to an exact credential
// set. A non-nil empty slice represents an empty auth group and must not
// fall back to all backend credentials.
OAuthAccountCredentials []string
// OAuthCredentialResolver optionally refreshes an auth group's exact file
// list while a ccl-launched Claude session is still running.
OAuthCredentialResolver func() ([]string, error)
MaxOutputTokens int // plain Responses only; 0 leaves SDK/default behavior
}
type UpstreamProtocol ¶ added in v1.3.4
type UpstreamProtocol string
const ( ProtocolOpenAIChat UpstreamProtocol = "openai_chat" ProtocolOpenAIResponses UpstreamProtocol = "openai_responses" )
type UsageModelTotals ¶ added in v1.4.0
type UsageModelTotals struct {
Model string
UsageTotals
}
UsageModelTotals pairs a model name with its accumulated totals.
type UsageTotals ¶ added in v1.4.0
type UsageTotals struct {
InputTokens int64
OutputTokens int64
CacheReadTokens int64
CacheWriteTokens int64
Requests int
}
UsageTotals accumulates token counts for one model across a session.
func (UsageTotals) TokenTotal ¶ added in v1.4.0
func (t UsageTotals) TokenTotal() int64
TokenTotal is InputTokens+OutputTokens, the number most reports lead with. Cache tokens are tracked separately: they are billed at a different rate and folding them in would make the total look larger than what was actually generated.
type UsageTracker ¶ added in v1.4.0
type UsageTracker struct {
// contains filtered or unexported fields
}
UsageTracker accumulates per-model token usage for a single ccl session.
One instance is shared by every runtime a session starts (a provider can run more than one backend, e.g. plain Responses fronted by the compatibility proxy), and it is safe for concurrent use because a streaming response and a retry can report on different goroutines.
func NewUsageTracker ¶ added in v1.4.0
func NewUsageTracker() *UsageTracker
NewUsageTracker returns an empty tracker.
func (*UsageTracker) Add ¶ added in v1.4.0
func (u *UsageTracker) Add(model string, input, output, cacheRead, cacheWrite int64)
Add records one request's usage against a model. An empty model name is recorded as "unknown" rather than silently discarded, so a gap in the underlying protocol's usage reporting is visible instead of invisible.
func (*UsageTracker) Snapshot ¶ added in v1.4.0
func (u *UsageTracker) Snapshot() ([]UsageModelTotals, bool)
Snapshot returns the accumulated totals ordered by first use, and whether anything was recorded at all.
Source Files
¶
- auth.go
- codex_cooldown.go
- copilot_auth.go
- copilot_runtime.go
- credentials.go
- debug.go
- doc.go
- kiro_auth.go
- kiro_converter.go
- kiro_credentials.go
- kiro_eventstream.go
- kiro_image.go
- kiro_models.go
- kiro_request_normalize.go
- kiro_response.go
- kiro_server.go
- qoder_auth.go
- qoder_converter.go
- qoder_credentials.go
- qoder_protocol.go
- qoder_runtime.go
- responses_compat.go
- runtime.go
- usage.go