Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 107 additions & 0 deletions docs/ai/design/2026-09-25-feature-claude-capacity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
---
phase: design
title: Claude Capacity Design
description: Read-only Claude OAuth usage provider using existing capacity contracts
---

# Claude Capacity Design

## Architecture Overview

```mermaid
flowchart LR
CLI[capacity provider selection] --> Manager[getClaudeCapacityReport]
Manager --> Credentials[Environment then profile file then default-profile macOS Keychain]
Credentials --> Probe[Claude OAuth usage GET]
Probe --> Normalize[CapacityReport and CapacityWindow]
Normalize --> Renderer[Existing text or JSON renderer]
```

The change adds one provider module under `packages/agent-manager/src/capacity/` and one direct branch in the existing CLI selector. The provider module owns credential resolution, request construction, response/error handling, and normalization. No generic provider layer is added.

## Data Models

- `CapacityReport`: unchanged; Claude reports `harness: "claude"`, `provider: "anthropic"`, generation time, auth/availability state, windows, and `creditsRemaining: null`.
- `CapacityWindow`: unchanged. Session, weekly, and model scopes use percentage/reset fields. Extra usage additionally uses `limitType: "CREDIT_LIMIT"` and monetary `total`, `current`, and `remaining` values.
- Claude credential input: OAuth access token plus optional expiry, resolved without returning metadata to callers.
- Claude usage payload: treated as untrusted `unknown`; parsers accept only finite numbers, valid timestamps, non-empty model identity, and plain objects/arrays.

## API Design

`getClaudeCapacityReport(options?)` mirrors the existing z.ai entry point and accepts injectable `now`, `env`, `readFile`, `keychainRead`, `platform`, `fetch`, and `timeoutMs` boundaries.

The provider sends:

```text
GET https://api.anthropic.com/api/oauth/usage
Authorization: Bearer <OAuth token>
Accept: application/json
Content-Type: application/json
anthropic-beta: oauth-2025-04-20
User-Agent: claude-code/2.1.0
```

The public report never contains the token, credential path, raw response body, or provider exception.

### Normalization contract

| Source | Window ID | Label | Duration |
| --------------------- | ---------------------------- | -------------------------- | -------------- |
| `five_hour` | `session` | Session | 300 minutes |
| `seven_day` | `weekly` | Weekly | 10,080 minutes |
| `seven_day_sonnet` | `claude:sonnet:weekly` | Sonnet weekly | 10,080 minutes |
| `seven_day_opus` | `claude:opus:weekly` | Opus weekly | 10,080 minutes |
| scoped `limits` entry | `claude:weekly:<model-slug>` | `<display name> weekly` | 10,080 minutes |
| enabled `extra_usage` | `claude:extra-usage` | `Extra usage · <currency>` | unknown |

Window parsers keep a valid object even when utilization or reset is absent, setting the corresponding normalized field to `null`. Invalid optional entries are skipped. Scoped entries require the observed `weekly_scoped`/`weekly` classification and a non-all-model identity; first occurrence wins on duplicate IDs.

Availability follows the existing capacity convention: `yes` when any window has utilization below 100, `no` when at least one utilization exists and all known utilizations are at least 100, and `unknown` when none is known.

### Failure contract

| Condition | Result |
| -------------------------------------- | ---------------------------------------------------------------- |
| Missing/malformed credential source | Sanitized credential error before fetch |
| Known expired file/Keychain credential | Sanitized expired-auth error before fetch |
| HTTP 401 | Sanitized unauthorized error |
| HTTP 403 | Sanitized forbidden error |
| HTTP 429 | Sanitized rate-limit error with normalized retry time when valid |
| Other non-2xx | Sanitized status-only request error |
| Network/abort | Sanitized request-failed error |
| Invalid JSON/non-object JSON | Sanitized malformed-response error |

These failures use ordinary provider-local errors because existing CLI behavior already propagates a single-provider failure and warns while retaining successful reports for a multi-provider request.

## Component Breakdown

- `capacity/claude.ts`: constants, credential resolution, parsing, availability calculation, bounded fetch, Retry-After parsing, and safe provider errors.
- `capacity/claude.ts`: provider-owned `ClaudeCapacityOptions`, credential resolution, request handling, and normalization.
- `capacity/index.ts`: `getClaudeCapacityReport` wrapper and option-type re-export with injected clock.
- `agent-manager/src/index.ts`: public export.
- `cli/commands/capacity.ts`: add `claude` to the supported union/list and direct reader branch.
- `cli/commands/capacity/render.ts`: add the `anthropic` display label; existing window rendering is reused.
- Provider fixtures/tests: credentials and usage shapes plus HTTP/error cases.
- CLI tests: selection, default provider set, JSON/text rendering, and partial failure.

## Design Decisions

| Decision | Choice | Rationale |
| ------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------- |
| Credential sources | Environment, profile file, then Keychain | Preserves explicit/profile precedence while supporting normal macOS Claude Code login. |
| Keychain | Default macOS profile only | Avoids mixing one global item into an explicitly selected custom profile. |
| Claude CLI fallback | Defer | Auth status has no usage; TUI scraping is interactive and brittle. |
| User-Agent version | Fixed fallback version | Satisfies the endpoint without spawning Claude or adding detection machinery. |
| Monetary data | Existing credit-limit fields | Preserves returned extra-usage values without changing the public contract. |
| Model limits | Known flat fields plus scoped `limits` | Covers observed endpoint shapes without guessing arbitrary fields. |
| Partial payloads | Preserve valid entries | Missing optional provider data must not erase truthful windows or become zero. |
| Provider selection | One direct CLI branch | Three providers do not justify a registry or new abstraction. |

## Non-Functional Requirements

- One bounded request per Claude probe; default timeout matches existing capacity providers.
- No credential writes, refresh, caching, retries, or Claude CLI calls. Keychain access uses fixed `/usr/bin/security` arguments without a shell, a 1.5-second timeout, and a bounded output buffer.
- All error messages are stable and omit response bodies and credentials.
- 429 exposes only a normalized retry time when `Retry-After` is valid.
- Parsing time is linear in the number of returned limits, with deterministic de-duplication.
- New and changed code targets complete branch coverage where practical and must pass repository lint, build, focused tests, and full tests.
87 changes: 87 additions & 0 deletions docs/ai/implementation/2026-09-25-feature-claude-capacity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
phase: implementation
title: Claude Capacity Implementation Record
description: Implementation details and validation evidence for Claude subscription capacity
---

# Claude Capacity Implementation Record

## Development Setup

- Active worktree: `.worktrees/feature-claude-capacity`
- Branch: `feature-claude-capacity`
- Bootstrap: `npm ci`
- Initial workspace build: `npm run build`
- No new dependencies or configuration files were added.

## Code Structure

- `packages/agent-manager/src/capacity/claude.ts`: provider-owned options, Claude credential resolution, usage parsing, request construction, timeout, and sanitized failures.
- `packages/agent-manager/src/capacity/index.ts`: clock-aware public report reader and option-type re-export.
- `packages/agent-manager/src/index.ts`: package-level public export.
- `packages/cli/src/commands/capacity.ts`: direct Claude provider selection alongside Codex and z.ai.
- `packages/cli/src/commands/capacity/render.ts`: Anthropic display label using the existing renderer.
- `packages/agent-manager/src/__tests__/capacity/fixtures/claude-*.json`: fake credential, complete, partial, and malformed fixtures.
- Provider and CLI capacity tests cover all requested behavior without live access.

## Implementation Notes

### Credential handling

- `CLAUDE_CODE_OAUTH_TOKEN` has precedence and bypasses file reads.
- A non-empty `CLAUDE_CONFIG_DIR` selects the profile root; relative values resolve against the injected/current working directory.
- The fallback path is `$HOME/.claude/.credentials.json` (or the operating-system home when `HOME` is absent).
- On macOS, a missing or tokenless default profile falls back to the `Claude Code-credentials` generic-password service through bounded `/usr/bin/security` execution.
- macOS controls access approval and may display a Keychain prompt on first use; implementation and tests never invoke the real reader.
- A custom `CLAUDE_CONFIG_DIR` never falls back to the global Keychain item, preventing cross-profile credential mixing.
- Only `claudeAiOauth.accessToken` and optional numeric millisecond `expiresAt` are read.
- Known-expired credentials fail before the fetch. Missing expiry is accepted.
- No refresh token, credential write, or Claude subprocess path exists.
- Profile-file and Keychain parsing use separate immutable credential results, keeping source precedence explicit and preventing state from one source being reused as another.

### Usage mapping

- Five-hour and seven-day account windows map to session and weekly capacity.
- Flat Sonnet and Opus weekly fields map independently.
- Weekly scoped limits use stable model slugs, ignore all-model scopes, keep the first duplicate, and do not filter on `is_active`.
- Enabled extra usage maps to the existing `CREDIT_LIMIT` fields after converting both returned minor-unit amounts to major units.
- Missing/invalid optional fields remain `null` or are skipped; they never become token totals.
- Availability is derived only from known utilization values.

### Request and errors

- One bounded GET is made to the OAuth usage endpoint with the exact Bearer, beta, JSON, and Claude Code User-Agent headers.
- 401, 403, 429, other non-success statuses, network failures, and malformed JSON have provider-local sanitized errors.
- Numeric and HTTP-date Retry-After values normalize against the injected clock.
- Response bodies, paths, and tokens are never included in errors or reports.

## Integration Points

- The existing multi-provider loop remains sequential and keeps successful reports when another provider fails.
- Explicit `Claude` input normalizes case-insensitively to `claude`.
- Default capacity now probes `codex`, `zai`, and `claude`.
- The existing renderer sorts windows by duration and renders credit totals without a Claude-specific path.

## Design Alignment

The implementation matches the approved provider-local design and unchanged public capacity contracts. There are no material design deviations. The fixed `claude-code/2.1.0` User-Agent intentionally avoids version-detection subprocess behavior.

## Validation Evidence

- Red/green cycles were observed for the public entry point, environment request, profile credential, Keychain fallback/profile isolation, expiry, complete mapping, 401, 403, 429, malformed JSON, network failure, package export, and CLI provider selection.
- Latest focused provider run: 27 tests passed, 0 failed.
- Latest focused CLI capacity run: 13 tests passed, 0 failed.
- Full agent-manager run: 57 files and 689 tests passed.
- Full CLI run: 102 files and 1,207 tests passed.
- Full repository run: all six workspaces passed, totaling 199 files and 2,307 tests.
- Full agent-manager coverage passed its thresholds: 88.54% statements, 77.31% branches, 93.58% functions, and 91.58% lines. `claude.ts` reached 94.89% statements, 85.71% branches, 95.83% functions, and 95.86% lines; the uncovered paths are a rejected injected Keychain reader and the real `/usr/bin/security` boundary that tests deliberately avoid invoking.
- Workspace build, repository lint, feature lint, new-file formatting, and `git diff --check` passed.
- A focused-only coverage invocation exited nonzero because package-global thresholds include unrelated files omitted by that focused run; the subsequent full package coverage run passed.
- Repository-wide formatting remains red on the same 37 files as the base worktree. A formatter check over every new source, test, fixture, and lifecycle document passed; modified existing files retain their baseline style to avoid unrelated formatting churn.
- Final design-alignment review found no blocking, important, or nice-to-have findings.

## Security Notes

- Tests contain only obvious fake tokens and fixture data.
- No live account call, real credential read, Keychain prompt, refresh, commit, or push occurred.
- All external boundaries are injected and fixture-backed.
106 changes: 106 additions & 0 deletions docs/ai/planning/2026-09-25-feature-claude-capacity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
---
phase: planning
title: Claude Capacity Implementation Plan
description: Ordered implementation and verification tasks for Claude subscription capacity
---

# Claude Capacity Implementation Plan

## Milestones

- [x] Milestone 1: Provider contract is specified by failing fixture-based tests.
- [x] Milestone 2: Claude provider and public agent-manager integration pass focused tests.
- [x] Milestone 3: CLI integration, documentation, and full validation pass.

## Task Breakdown

### Phase 1: Provider contract and fixtures

- [x] Task 1.1: Add obviously fake credential, complete usage, partial usage, and malformed usage fixtures.
- Outcome: tests never depend on live credentials or endpoints.
- Dependencies: approved requirements/design.
- Validation: fixtures parse only through test code and contain no real-looking account data.
- Scenarios: all Test Data items.
- [x] Task 1.2: Add failing credential-resolution tests for environment precedence, default/custom profile paths, missing/malformed auth, and expiry.
- Outcome: read-only credential contract is executable.
- Dependencies: Task 1.1 credential fixture.
- Validation: focused Vitest run fails for missing implementation rather than test setup.
- Scenarios: Credential resolution checklist.
- [x] Task 1.3: Add failing parser/request tests for all usage mappings, partial/malformed payloads, exact headers, timeout, sanitized status/network failures, and Retry-After forms.
- Outcome: API and normalization contracts are executable before production code.
- Dependencies: Task 1.1 usage fixtures.
- Validation: focused Vitest run demonstrates red state.
- Scenarios: Usage parsing and HTTP probing checklists.

### Phase 2: Agent-manager implementation

- [x] Task 2.1: Implement `capacity/claude.ts` credential resolution and parsing with injected boundaries.
- Outcome: environment/profile credentials resolve without writes; default-profile macOS users additionally receive bounded Keychain fallback.
- Dependencies: Task 1.2.
- Validation: credential tests pass.
- [x] Task 2.2: Implement usage normalization for core, model, scoped, and extra-usage windows.
- Outcome: endpoint data maps into unchanged capacity contracts without token-total inference.
- Dependencies: Task 1.3.
- Validation: parser tests pass, including partial fixtures.
- [x] Task 2.3: Implement the bounded request and safe HTTP/error mapping.
- Outcome: exact request contract and sanitized errors for 401/403/429/other/network/malformed JSON.
- Dependencies: Tasks 2.1-2.2.
- Validation: HTTP tests pass and assert no fake secret/body leakage.
- [x] Task 2.4: Export `getClaudeCapacityReport` and verify its injected clock/report integration.
- Outcome: agent-manager exposes the third capacity provider without a registry.
- Dependencies: Tasks 2.1-2.3.
- Validation: index integration and full agent-manager suites pass.

### Phase 3: CLI integration and lifecycle evidence

- [x] Task 3.1: Add Claude to supported provider selection and the existing direct reader.
- Outcome: explicit and default commands include Claude while preserving de-duplication and aliases.
- Dependencies: Task 2.4.
- Validation: CLI selection and failure-behavior tests pass.
- [x] Task 3.2: Add the Anthropic display label and mocked report rendering coverage.
- Outcome: text and JSON use the existing renderer without a Claude-specific presentation path.
- Dependencies: Task 3.1.
- Validation: renderer tests pass.
- [x] Task 3.3: Reconcile implementation/testing documents and checklist status.
- Outcome: lifecycle docs describe actual code, deviations, and fresh evidence.
- Dependencies: all implementation tasks.
- Validation: feature lint and `git diff --check` pass.
- [x] Task 3.4: Run focused tests, build, full repository tests, and final review.
- Outcome: implementation is evidence-backed and ready for user inspection.
- Dependencies: Tasks 3.1-3.3.
- Validation: commands listed in the testing strategy complete successfully.

## Dependencies

- Tasks execute in numeric order; CLI integration depends on the public agent-manager export.
- No external account, real credential, live network, Claude subprocess, migration, or new package dependency is required for implementation and tests. Keychain behavior is exercised only through an injected fake reader.
- Existing `CapacityReport`, `CapacityWindow`, renderer, and multi-provider failure behavior are load-bearing contracts and must remain backward compatible.

## Timeline & Estimates

- Provider tests and fixtures: small.
- Provider implementation: medium, with the largest risk in partial payload and error semantics.
- CLI integration: small.
- Full validation and review: medium because the monorepo suite must run.

Work proceeds continuously in this lifecycle run; estimates communicate relative complexity rather than delivery dates.

## Risks & Mitigation

- Anthropic payload drift: parse untrusted values conservatively and retain valid partial data.
- Secret leakage: never include auth input or response bodies in errors; assert this with fake sentinel values.
- Profile mismatch: derive one credential path from the injected environment and never use the global Keychain fallback for a custom `CLAUDE_CONFIG_DIR`.
- Monetary-unit confusion: convert both returned amounts consistently and label the currency; never map them as tokens.
- Over-generalization: keep all new logic provider-local and add no flag, registry, base class, or fallback without a current caller.
- Regression in default command behavior: update exact provider-set and partial-failure CLI tests.

## Resources Needed

- Existing Node/TypeScript/Vitest workspace only.
- Supplied CodexBar Claude provider as the behavioral reference.
- Existing Codex and z.ai capacity implementations as repository conventions.
- Fixture-based mocks for all external boundaries.

## Progress Summary

All planned provider, CLI, documentation, and validation tasks are complete. The final review found no blocking or important issues. Fresh evidence includes 27 Claude provider tests, 13 capacity CLI tests, and the full 2,307-test repository suite passing. The approved Keychain extension remains provider-local and adds no general abstraction.
Loading
Loading