claude

package
v0.10.13 Latest Latest
Warning

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

Go to latest
Published: May 7, 2026 License: MIT Imports: 20 Imported by: 0

Documentation

Index

Constants

View Source
const ClaudeModelDiscoveryFreshnessWindow = 24 * time.Hour
View Source
const DefaultClaudeQuotaStaleAfter = 15 * time.Minute

DefaultClaudeQuotaStaleAfter is the default maximum age before a cached snapshot is considered stale and foreground routing should fall back to the safe default.

Variables

This section is empty.

Functions

func ClaudeQuotaCachePath

func ClaudeQuotaCachePath() (string, error)

ClaudeQuotaCachePath returns the durable location for the Claude quota cache. It resolves to $XDG_STATE_HOME/<config-dir>/claude-quota.json, or ~/.local/state/<config-dir>/claude-quota.json when XDG_STATE_HOME is unset. The FIZEAU_CLAUDE_QUOTA_CACHE env var takes precedence (primarily for tests).

func ClaudeQuotaSnapshotAge

func ClaudeQuotaSnapshotAge(snapshot *ClaudeQuotaSnapshot, now time.Time) time.Duration

ClaudeQuotaSnapshotAge reports the age of a snapshot relative to now. A zero or future CapturedAt yields a zero age.

func DefaultClaudeModelDiscovery

func DefaultClaudeModelDiscovery() harnesses.ModelDiscoverySnapshot

func IsClaudeQuotaExhaustedMessage added in v0.10.5

func IsClaudeQuotaExhaustedMessage(text string) bool

IsClaudeQuotaExhaustedMessage recognizes Claude CLI quota failures that are emitted as plain text rather than structured quota data. Claude currently reports weekly exhaustion with wording like "out of extra usage", so callers must treat these strings as a hard quota signal.

func IsClaudeQuotaFresh

func IsClaudeQuotaFresh(snapshot *ClaudeQuotaSnapshot, now time.Time, staleAfter time.Duration) bool

IsClaudeQuotaFresh reports whether a snapshot exists and is newer than staleAfter relative to now. A nil snapshot is never fresh. A zero staleAfter falls back to DefaultClaudeQuotaStaleAfter.

func MarkClaudeQuotaExhaustedFromMessage added in v0.10.5

func MarkClaudeQuotaExhaustedFromMessage(text string, now time.Time) bool

MarkClaudeQuotaExhaustedFromMessage records a runtime Claude quota failure in the durable cache so later automatic routing avoids Claude until a fresh quota probe proves headroom again.

func ReadClaudeModelDiscoveryFromCassette

func ReadClaudeModelDiscoveryFromCassette(dir string) (harnesses.ModelDiscoverySnapshot, error)

func ReadClaudeModelDiscoveryViaPTY

func ReadClaudeModelDiscoveryViaPTY(timeout time.Duration, opts ...QuotaPTYOption) (harnesses.ModelDiscoverySnapshot, error)

func ReadClaudeQuotaFromCassette

func ReadClaudeQuotaFromCassette(dir string) ([]harnesses.QuotaWindow, *harnesses.AccountInfo, error)

func ReadClaudeQuotaViaPTY

func ReadClaudeQuotaViaPTY(timeout time.Duration, opts ...QuotaPTYOption) ([]harnesses.QuotaWindow, *harnesses.AccountInfo, error)

func ReadClaudeQuotaViaTmux deprecated

func ReadClaudeQuotaViaTmux(timeout time.Duration) ([]harnesses.QuotaWindow, *harnesses.AccountInfo, error)

ReadClaudeQuotaViaTmux starts claude in a detached tmux session, sends /usage, captures the pane output, and returns parsed quota windows and account info.

Deprecated: this is a diagnostic-only legacy path. Supported quota probes use ReadClaudeQuotaViaPTY so accepted evidence passes through direct PTY cassettes. Returns an error if tmux or claude are not found, or probing times out.

func ReadClaudeReasoningFromHelp

func ReadClaudeReasoningFromHelp(ctx context.Context, binary string, args ...string) ([]string, error)

func RefreshClaudeQuotaAsync

func RefreshClaudeQuotaAsync(capture func() (ClaudeQuotaSnapshot, error))

RefreshClaudeQuotaAsync launches a background goroutine that invokes capture and writes the result to the default cache path. It returns immediately; callers never block on capture.

The capture function is injected so that the durable cache layer does not depend on any specific PTY implementation. A future `ddx claude refresh-quota` subcommand can pass a real PTY-backed capture; tests can pass deterministic fakes.

If capture returns an error, the cache is not touched.

func ResolveClaudeFamilyAlias

func ResolveClaudeFamilyAlias(model string, snapshot harnesses.ModelDiscoverySnapshot) string

func WriteClaudeQuota

func WriteClaudeQuota(path string, snapshot ClaudeQuotaSnapshot) error

WriteClaudeQuota atomically persists a ClaudeQuotaSnapshot to the given path. The parent directory is created if necessary. The file is written to a sibling .tmp file and renamed into place so readers never observe a partially-written snapshot. The final file mode is 0600.

Types

type ClaudeQuotaRoutingDecision

type ClaudeQuotaRoutingDecision struct {
	// PreferClaude is true when a fresh snapshot shows headroom in both the
	// 5-hour and weekly windows. When false, routing should prefer a
	// non-claude fallback harness.
	PreferClaude bool
	// SnapshotPresent is true when a snapshot was found in the cache (even
	// if stale).
	SnapshotPresent bool
	// Fresh is true when the snapshot is present and newer than staleAfter.
	Fresh bool
	// Age is the age of the snapshot relative to now (zero when absent).
	Age time.Duration
	// Snapshot is the cached snapshot when present.
	Snapshot *ClaudeQuotaSnapshot
	// Reason describes why the decision was made (diagnostic surface).
	Reason string
}

ClaudeQuotaRoutingDecision summarises what foreground routing should do given the current cached snapshot.

func DecideClaudeQuotaRouting

func DecideClaudeQuotaRouting(snapshot *ClaudeQuotaSnapshot, now time.Time, staleAfter time.Duration) ClaudeQuotaRoutingDecision

DecideClaudeQuotaRouting turns a cached snapshot into a routing decision for foreground callers. When the snapshot is missing or stale, the safe default is NOT to prefer claude (assume limited).

A snapshot counts as "limited" when either window reports zero or negative remaining headroom.

func ReadClaudeQuotaRoutingDecision

func ReadClaudeQuotaRoutingDecision(now time.Time, staleAfter time.Duration) ClaudeQuotaRoutingDecision

ReadClaudeQuotaRoutingDecision is a convenience wrapper that reads the default cache and produces a routing decision in one call. It is the entry point foreground routing should use instead of any inline PTY capture.

type ClaudeQuotaSnapshot

type ClaudeQuotaSnapshot struct {
	CapturedAt        time.Time               `json:"captured_at"`
	FiveHourRemaining int                     `json:"five_hour_remaining"`
	FiveHourLimit     int                     `json:"five_hour_limit"`
	WeeklyRemaining   int                     `json:"weekly_remaining"`
	WeeklyLimit       int                     `json:"weekly_limit"`
	Windows           []harnesses.QuotaWindow `json:"windows,omitempty"`
	Source            string                  `json:"source"` // e.g. "pty", "heuristic"
	Account           *harnesses.AccountInfo  `json:"account,omitempty"`
}

ClaudeQuotaSnapshot captures Claude's current-quota headroom as absolute token/message counts. It is written to a durable per-user cache by an asynchronous capture path and read by foreground routing consumers.

The snapshot is intentionally distinct from the percentage-based QuotaSignal: foreground routing needs concrete numbers to reason about 5-hour / weekly headroom without invoking PTY capture inline.

func ReadClaudeQuota

func ReadClaudeQuota() (*ClaudeQuotaSnapshot, bool)

The second return value is false if no snapshot is present or cannot be decoded.

Callers SHOULD check snapshot age via ClaudeQuotaSnapshotAge (or IsClaudeQuotaFresh) before trusting the values; this function does not itself enforce a TTL so that callers can report stale snapshots in diagnostic surfaces like `ddx agent doctor --routing`.

func ReadClaudeQuotaFrom

func ReadClaudeQuotaFrom(path string) (*ClaudeQuotaSnapshot, bool)

ReadClaudeQuotaFrom reads the snapshot at the given path. Returns (nil, false) if the file does not exist or cannot be decoded. Non- existence is NOT an error: foreground callers are expected to fall back to a safe default when no snapshot is present.

func RefreshClaudeQuotaViaPTY

func RefreshClaudeQuotaViaPTY(timeout time.Duration, opts ...QuotaPTYOption) (ClaudeQuotaSnapshot, error)

type QuotaPTYOption

type QuotaPTYOption func(*quotaPTYOptions)

func WithQuotaPTYCassetteDir

func WithQuotaPTYCassetteDir(dir string) QuotaPTYOption

func WithQuotaPTYCommand

func WithQuotaPTYCommand(binary string, args ...string) QuotaPTYOption

func WithQuotaPTYEnv

func WithQuotaPTYEnv(env ...string) QuotaPTYOption

func WithQuotaPTYWorkdir

func WithQuotaPTYWorkdir(workdir string) QuotaPTYOption

type Runner

type Runner struct {
	// Binary is the absolute path to the claude executable. When empty the
	// runner resolves "claude" via PATH at Execute time.
	Binary string

	// BaseArgs is prepended to the per-request argument list; callers use
	// it to pin a consistent invocation profile (e.g. ["--print", "-p",
	// "--output-format", "stream-json", "--verbose"]).
	BaseArgs []string

	// PromptMode controls how the prompt is delivered to claude:
	//   "stdin" (default) — prompt is piped on stdin
	//   "arg"             — prompt is appended as the final positional argument
	PromptMode string

	// EventBuffer overrides the per-Execute channel buffer size. Zero
	// selects defaultEventBuffer.
	EventBuffer int
}

Runner is the subprocess-backed claude harness. It launches the claude CLI in stream-json mode, parses each line into harness Events, and emits a final Event when the subprocess exits. On ctx.Done(), the subprocess (and any forked children belonging to its process group) is signalled SIGTERM and reaped so PTY/tool children don't outlive the request.

func (*Runner) Execute

func (r *Runner) Execute(ctx context.Context, req harnesses.ExecuteRequest) (<-chan harnesses.Event, error)

Execute runs one resolved request through the claude CLI and emits stream-derived events on the returned channel. The channel is closed once a final event has been emitted. PTY/orphan children are reaped on ctx.Done(). If the CLI rejects stream-json flags (older build), the runner falls back to a buffered legacy invocation and emits a single text_delta + final from the buffered output.

func (*Runner) HealthCheck

func (r *Runner) HealthCheck(ctx context.Context) error

HealthCheck verifies the claude binary resolves on PATH (or at the configured Binary). It does NOT invoke the binary so it stays cheap and safe to call from request hot paths. A future extension can probe quota state via the cache layer.

func (*Runner) Info

func (r *Runner) Info() harnesses.HarnessInfo

Info returns identity + capability metadata for this harness.

Path is best-effort: the runner reports Binary if set, otherwise looks up "claude" on PATH. Available tracks whether the lookup succeeded so callers can show a useful error in `ddx agent list` without invoking HealthCheck synchronously.

Jump to

Keyboard shortcuts

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