oauthproxy

package
v1.5.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT Imports: 59 Imported by: 0

Documentation

Overview

Package oauthproxy implements ccl's local subscription and protocol runtimes.

Claude Code talks to an Anthropic Messages endpoint. CCL directly owns every Codex Responses data plane: API-key gateways, GPT subscriptions, and Copilot models whose catalog endpoint is Responses. Manual OpenAI Chat plus Gemini/Grok/Kimi/Claude subscriptions still use the embedded CLIProxyAPI SDK. Copilot Chat and native Messages models use CPA behind CCL's protocol router. WorkBuddy uses CCL-owned auth, refresh, catalog, and upstream headers around a CPA Chat compatibility child. Kiro and Qoder use CCL's direct runtimes. Direct Anthropic API-key gateways bypass this package entirely.

Error recovery follows the data-plane owner. CPA-backed providers use CPA's native retry/cooldown and Retry-After handling without a CCL result hook. Codex Responses refreshes GPT OAuth once after a 401 and otherwise preserves upstream status/Retry-After. WorkBuddy refreshes once after a 401/403 and leaves 429/5xx untouched. Copilot, Qoder, and Kiro keep only the recovery behavior required by their upstreams; notably Kiro rotates credentials and retries burst 429s after 1s, 2s, and 4s.

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:

  1. Codex Responses ownership (codex_responses_*.go) CCL owns Messages-to-Responses translation, Codex identity headers, GPT token refresh, upstream errors, Responses SSE decoding, and usage. CPA's codex executor must never be inserted into these paths. Its source may be consulted for compatibility, but a CPA upgrade must not change the wire.

  2. 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.

  3. 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.

  4. 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. GPT login is initiated by CPA's authenticator, but CCL reads and refreshes the selected token in its direct Responses runtime.

  5. Model registration cleanup CPA runtime Stop unregisters every auth ID from cliproxy.GlobalModelRegistry so a later provider does not inherit another backend's routes. CCL direct runtimes do not register global models.

  6. 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 according to its advertised Chat, Responses, or Messages endpoint. Responses models use CCL's Codex converter; only Chat and native Messages use a CPA compatibility child. Do not bypass the Copilot gateway's own client identity or credential rotation.

  7. 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.

  8. Kiro direct runtime (kiro_*.go) Kiro Portal PKCE / Builder ID auth, credential refresh, model discovery, Messages-to-Amazon-Q conversion, retry, and AWS EventStream decoding all run in ccl. Do not route Kiro traffic through CPA unless the complete direct-runtime behavior is deliberately replaced and regression-tested.

  9. WorkBuddy hybrid runtime (workbuddy_*.go) CCL owns the official external-link login polling, credential refresh, /v3/config model catalog, and WorkBuddy identity/session headers. CPA owns only Anthropic Messages <-> OpenAI Chat Completions conversion. Do not move WorkBuddy auth or provider-specific error recovery into CPA.

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, ccl oauth workbuddy, an openai_responses API-key provider, and a plain openai(chat) provider with streaming + tool calls.

Index

Constants

View Source
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"
	ProviderWorkBuddy     = "workbuddy"
)
View Source
const (
	KiroAuthModePortal    = "portal"
	KiroAuthModeBuilderID = "builder"
)

Variables

This section is empty.

Functions

func AuthDir

func AuthDir() (string, error)

func BackendProvider

func BackendProvider(providerName string) (string, error)

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

func DebugHTTPBody(label string, body []byte)

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

func EnsureSessionLog(prefix string) (path string, owned bool, err error)

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 LogDebugEvent added in v1.5.0

func LogDebugEvent(event string, attrs ...any)

Event helpers keep the hot-path diagnostics machine-searchable while the older printf helpers remain available for low-volume lifecycle messages.

func LogDebugf added in v1.4.0

func LogDebugf(format string, args ...any)

LogDebugf writes sensitive or high-volume detail visible only with `ccl log --level debug`.

func LogDir added in v1.4.0

func LogDir() (string, error)

LogDir is ~/.ccl/logs, where ccl keeps its diagnostics.

func LogEnabled added in v1.4.0

func LogEnabled() bool

LogEnabled reports whether ccl's current session file is active.

func LogErrorEvent added in v1.5.0

func LogErrorEvent(event string, attrs ...any)

func LogErrorf added in v1.4.0

func LogErrorf(format string, args ...any)

func LogFilePath added in v1.4.0

func LogFilePath() string

LogFilePath reports the active session log path, or an empty string when off.

func LogInfoEvent added in v1.5.0

func LogInfoEvent(event string, attrs ...any)

func LogInfof added in v1.4.0

func LogInfof(format string, args ...any)

LogInfof writes a normal runtime event. Existing ccl diagnostics use this level so `ccl log on` is useful without exposing request payloads.

func LogUpstreamEvent added in v1.5.0

func LogUpstreamEvent(status int, event string, attrs ...any)

func LogUpstreamStatusf added in v1.4.0

func LogUpstreamStatusf(status int, format string, args ...any)

LogUpstreamStatusf classifies HTTP status records consistently. Successful per-request records are DEBUG; client failures are WARN; server failures are ERROR.

func LogWarnEvent added in v1.5.0

func LogWarnEvent(event string, attrs ...any)

func LogWarnf added in v1.4.0

func LogWarnf(format string, args ...any)

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 SafeLogEndpoint added in v1.5.0

func SafeLogEndpoint(raw string) string

SafeLogEndpoint keeps a URL useful for routing diagnostics without retaining userinfo, query parameters, or fragments, which commonly carry API keys on third-party gateways.

func SessionLogPath added in v1.4.0

func SessionLogPath(session string) string

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

func SetLogLevel(level LogLevel, path string) error

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

func ValidateLoginProvider(providerName string) (string, error)

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
	Disabled      bool
	Unavailable   bool
	QuotaExceeded bool
}

CredentialInfo is the non-secret state doctor reads from ~/.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 ListCredentials added in v1.3.13

func ListCredentials() ([]CredentialInfo, error)

ListCredentials reads supported JSON files directly inside ~/.ccl/auth. Subdirectories and unrelated JSON files are ignored.

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.

const (
	LogLevelOff   LogLevel = "off"
	LogLevelDebug LogLevel = "debug"
	LogLevelInfo  LogLevel = "info"
	LogLevelWarn  LogLevel = "warn"
	LogLevelError LogLevel = "error"
)

func CurrentLogLevel added in v1.4.0

func CurrentLogLevel() LogLevel

CurrentLogLevel reports the active logging threshold.

func ParseLogLevel added in v1.4.0

func ParseLogLevel(raw string) (LogLevel, bool)

ParseLogLevel accepts ccl's standard logging levels.

type LoginOptions

type LoginOptions struct {
	NoBrowser    bool
	CallbackPort int
	KiroAuthMode string
}

type LoginResult

type LoginResult struct {
	Provider string
	Backend  string
	Path     string
}

func Login

func Login(ctx context.Context, providerName string, opts LoginOptions) (LoginResult, error)

type Runtime

type Runtime struct {
	// contains filtered or unexported fields
}

func StartOAuth added in v1.3.4

func StartOAuth(parent context.Context, providerName, modelSpec, credentialFile string) (*Runtime, error)

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) (*Runtime, error)

StartOpenAIResponsesAPI starts CCL's Codex Responses adapter against an API key gateway. Request conversion, Codex identity, SSE conversion, errors, and usage accounting are all owned by CCL and cannot change with a CPA upgrade.

func StartProvider added in v1.3.4

func StartProvider(parent context.Context, options StartOptions) (*Runtime, error)

StartProvider starts a loopback Anthropic Messages adapter. Responses gateways use CCL's Codex Responses implementation; CPA remains responsible only for protocol families that have not moved to a CCL-owned data plane.

func (*Runtime) APIKey

func (r *Runtime) APIKey() string

func (*Runtime) ClaudeBaseURL added in v1.3.4

func (r *Runtime) ClaudeBaseURL() string

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) Endpoint

func (r *Runtime) Endpoint() string

func (*Runtime) ListAuths added in v1.3.13

func (r *Runtime) ListAuths() []*coreauth.Auth

ListAuths returns the credentials currently loaded in this runtime, already filtered to the OAuth backend and selected account.

func (*Runtime) ModelDisplayNames added in v1.4.0

func (r *Runtime) ModelDisplayNames() map[string]string

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

func (r *Runtime) Models() []string

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
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL