oauthproxy

package
v1.5.5 Latest Latest
Warning

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

Go to latest
Published: Aug 28, 2026 License: MIT Imports: 58 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 data plane: Codex Responses (GPT and openai_responses API-key gateways), OpenAI Chat (manual API-key providers, Grok, Kimi, WorkBuddy, and Copilot Chat models), the native-Anthropic Messages passthrough (models.dev @ai-sdk/anthropic models and Copilot native-Messages models), and Gemini (Antigravity conversion). Copilot's mixed catalog, Kiro, Qoder, and Command Code (a direct /alpha/generate NDJSON data plane) run entirely on CCL-owned runtimes too. Direct Anthropic API-key gateways bypass this package altogether.

Error recovery follows the data-plane owner. CCL-owned data planes refresh OAuth once after a 401 and otherwise preserve upstream status/Retry-After: Codex Responses, Grok, and Kimi refresh once; WorkBuddy refreshes once after a 401/403; Gemini also falls back from the daily to the prod Antigravity base on network errors, 429s and 5xx responses. 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.

Direct data planes

Each backend's data plane is CCL-owned end-to-end. Treat these as a regression checklist for keeping provider traffic on the corresponding CCL runtime:

  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.

  2. GitHub Copilot direct gateway (copilot_runtime.go) Copilot authenticates with GitHub, discovers the account's authoritative model catalog, and routes each model according to its advertised Chat, Responses, or Messages endpoint — all three served by CCL data planes. Do not bypass the Copilot gateway's own client identity or credential rotation.

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

  4. 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. Keep Kiro's direct request path and upstream identity intact.

  5. WorkBuddy runtime (workbuddy_*.go) CCL owns the official external-link login polling, credential refresh, /v3/config model catalog, WorkBuddy identity/session headers, and the Anthropic Messages <-> OpenAI Chat Completions conversion (via the shared chatCompletionsService).

  6. Native Messages passthrough (anthropic_passthrough.go) The anthropicPassthroughService serves native Anthropic models for models.dev @ai-sdk/anthropic models and Copilot native-Messages models (static key). It resolves the upstream credential and refreshes once after a 401.

  7. Command Code direct runtime (commandcode_*.go) CCL owns the /alpha/generate NDJSON conversion, client identity, device registration handshake, error mapping, usage accounting, and the static model catalog. Command Code answers are surfaced as Anthropic Messages text blocks so the protocol gap is invisible to Claude Code. Credentials arrive on two paths: `ccl oauth commandcode` mirrors the official CLI login (open the studio "Get API key" page, accept the key back through the loopback callback or as a manual paste, validate via /alpha/whoami), and `ccl import commandcode` reads the official CLI's long-lived key from ~/.commandcode/auth.json and validates it the same way. Both store the result under a deterministic per-account file in ~/.ccl/auth (0600); the legacy commandcode.json binding remains loadable for compatibility. Do not route Command Code traffic through a third-party proxy.

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

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"
	ProviderWorkBuddy     = "workbuddy"
	ProviderCommandCode   = "commandcode"
)
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 CommandCodeModelCatalog added in v1.5.4

func CommandCodeModelCatalog() []protocol.ModelInfo

CommandCodeModelCatalog returns the authoritative Command Code model catalog as ModelInfo pairs. The gateway has no /v1/models route, so model metadata commands and the set/map flows read this static list instead of probing.

func CommandCodeProbeInit added in v1.5.4

func CommandCodeProbeInit(ctx context.Context, endpoint, upstreamAPIKey string, timeout time.Duration) (int, string, error)

CommandCodeProbeInit checks a candidate Command Code endpoint with the lightweight GET /alpha/whoami first: any HTTP status it returns (even 404) is conclusive about the endpoint, and 2xx with the candidate key means the gateway is Command Code. Only a transport error — e.g. a filtered network that drops the route — falls back to the reference-shaped POST /alpha/fingerprint/record handshake, which protocol detection previously used as its only signal. A non-2xx status means "not detected"; the caller maps 401/403 to an invalid-key hint.

func CommandCodeProbeWhoami added in v1.5.4

func CommandCodeProbeWhoami(ctx context.Context, endpoint, upstreamAPIKey string, timeout time.Duration) (int, string, error)

CommandCodeProbeWhoami checks the configured Command Code gateway directly with GET /alpha/whoami. Unlike CommandCodeProbeInit, it never falls back to the fingerprint handshake: callers such as doctor need to report the real upstream authentication/connectivity result rather than a different endpoint's status.

func CommandCodeSupportsModel added in v1.5.4

func CommandCodeSupportsModel(model string) bool

CommandCodeSupportsModel reports whether the catalog admits a model ID, case-insensitively. Availability probes use it: a request for a model the gateway does not serve fails before any traffic leaves the machine.

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. Command Code uses a browser "Get API key" page plus manual paste (its non-browser alternative is ImportCredential via `ccl import commandcode`).

Types

type AuthInfo added in v1.5.3

type AuthInfo struct {
	ID             string
	Provider       string
	FileName       string
	Label          string
	Status         Status
	StatusMessage  string
	Disabled       bool
	Unavailable    bool
	Metadata       map[string]any
	Quota          QuotaState
	NextRetryAfter time.Time
}

AuthInfo is ccl's self-owned view of a loaded OAuth credential used by Runtime.ListAuths. It carries only the fields needed by doctor diagnostics and the per-backend listAuths implementations, while retaining the existing field surface so consumers and tests need no semantic changes.

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 persisted account health when present in 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
	// Stdin feeds manual credential entry (Command Code API-key paste); nil
	// disables the interactive paste path.
	Stdin io.Reader
	// StdinCancel optionally interrupts a blocking Stdin read when the login
	// finishes. Login never closes Stdin implicitly because callers retain
	// ownership of the reader (notably os.Stdin); provide this hook only when
	// the caller can safely interrupt its reader.
	StdinCancel func()
}

type LoginResult

type LoginResult struct {
	Provider string
	Backend  string
	Path     string
}

func ImportCredential added in v1.3.13

func ImportCredential(ctx context.Context, providerName string) (LoginResult, error)

ImportCredential is the non-browser import path for backends whose official CLI stores a long-lived key instead of offering a third-party OAuth flow. Browser/device OAuth stays on Login; imports stay here so `ccl oauth` never advertises a backend that cannot do OAuth.

func Login

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

type QuotaState added in v1.5.3

type QuotaState struct {
	// Exceeded indicates the credential recently hit a quota error.
	Exceeded bool
	// Reason provides an optional provider-specific description.
	Reason string
	// NextRecoverAt is when the credential may become available again.
	NextRecoverAt time.Time
	// BackoffLevel stores the progressive cooldown exponent for rate limits.
	BackoffLevel int
}

QuotaState captures recent quota information for a credential, surfaced to the doctor diagnostics when a backend reports it has been rate-limited.

type Runtime

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

func StartCommandCodeAPI added in v1.5.4

func StartCommandCodeAPI(parent context.Context, endpoint, upstreamAPIKey, modelSpec string) (*Runtime, error)

StartCommandCodeAPI starts CCL's self-owned Command Code data plane against a Command Code API key. Conversion, NDJSON/SSE handling, identity headers, the fingerprint/lifecycle handshake, error mapping, and usage accounting are all owned by CCL and independent of external runtime upgrades. modelSpec is accepted for StartOptions symmetry only: the runtime serves the authoritative 26-model catalog and never rewrites requested model IDs.

func StartMixedProtocolAPIKeyRuntime added in v1.5.3

func StartMixedProtocolAPIKeyRuntime(parent context.Context, endpoint, upstreamAPIKey, modelSpec string, protocols map[string]string) (*Runtime, error)

StartMixedProtocolAPIKeyRuntime starts a single Anthropic Messages entrypoint that routes each request to the correct upstream protocol for the requested model: native Anthropic through a CCL passthrough, OpenAI Chat through CCL's Chat Completions adapter, and OpenAI Responses through CCL's Codex Responses adapter. It is the API-key gateway counterpart to the Copilot OAuth router.

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 CCL's self-owned Chat Completions adapter against an OpenAI-compatible API-key gateway. Request conversion, SSE conversion, error mapping, and usage accounting are all owned by CCL and cannot change with an external runtime implementation upgrade.

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 independent of external runtime upgrades.

func StartProvider added in v1.3.4

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

StartProvider starts a loopback Anthropic Messages adapter. Every protocol family is served by a CCL-owned data plane (Codex Responses, OpenAI Chat, or the native Anthropic passthrough).

func (*Runtime) APIKey

func (r *Runtime) APIKey() string

func (*Runtime) CheckUpstream added in v1.5.4

func (r *Runtime) CheckUpstream(ctx context.Context) (int, string, error)

CheckUpstream validates a runtime-owned upstream credential without exposing that credential to the caller. It is unsupported for runtimes that do not provide a safe health callback.

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() []*AuthInfo

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 cancels the run context, waits for Serve to exit on its own, and only force-calls http.Server.Shutdown if that wait times out.

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
	// ModelProtocols maps a lowercase model ID to its upstream protocol for the
	// mixed-protocol models.dev gateway. When non-empty it takes precedence over
	// the single Protocol value and starts the mixed-protocol router.
	ModelProtocols map[string]string
}

type Status added in v1.5.3

type Status string

Status represents the lifecycle state of an AuthInfo entry. It mirrors the status vocabulary the doctor surface consumes: a credential is active, intentionally disabled, temporarily in error, or in some pending/unknown state.

const (
	// StatusUnknown means the auth state could not be determined.
	StatusUnknown Status = "unknown"
	// StatusActive indicates the credential is valid and ready for use.
	StatusActive Status = "active"
	// StatusPending indicates the credential is waiting for an external action.
	StatusPending Status = "pending"
	// StatusError indicates the credential is temporarily unavailable.
	StatusError Status = "error"
	// StatusDisabled marks the credential as intentionally disabled.
	StatusDisabled Status = "disabled"
)

type UpstreamProtocol added in v1.3.4

type UpstreamProtocol string
const (
	ProtocolOpenAIChat      UpstreamProtocol = "openai_chat"
	ProtocolOpenAIResponses UpstreamProtocol = "openai_responses"
	ProtocolCommandCode     UpstreamProtocol = "commandcode"
)

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