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
4 changes: 2 additions & 2 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
**@cortexkit/opencode-anthropic-auth (OpenCode Plugin):**
- Purpose: OpenCode plugin that intercepts Anthropic fetch requests, provides CLI, TUI sidebar, and command modal dialogs
- Location: `packages/opencode/src/`
- Contains: Plugin entry point (`index.ts`), CLI (`cli.ts`), request transform/SSE stripping (`transform.ts`), internal request policy and killswitch messaging (`request-policy.ts`), cache diagnostics tracking and accounting (`cache-diagnostics.ts`), Anthropic server-side safety fallback (`server-fallback.ts`), deterministic client content-filter recovery (`fable-fallback.ts`), system prompt sanitization/memoization (`sanitize-memo.ts`), message-history context reconstruction (`prompt-context.ts`), request-scoped Fable 5.1 effort history (`effort-history.ts`), user-message prompt UUID and request ID lineage tracking across tool loops (`billing-lineage.ts`), lane-start request correlation (`lane-start.ts`), quota priming manager registry (`prime-manager-registry.ts`), scoped Claustrum startup admission (`custody-mode.ts`, `custody-dimensions.ts`), offline setup wizard (`setup/`), TUI sidebar widget (`tui.tsx`), TUI preferences (`tui-preferences.ts`), command modal dialogs (`tui/command-dialogs.tsx`), loopback RPC server/client for TUI IPC (`rpc/`), and TUI sidebar IPC state management (`sidebar-state.ts`)
- Contains: Plugin entry point (`index.ts`), CLI (`cli.ts`), request transform/SSE stripping (`transform.ts`), internal request policy and killswitch messaging (`request-policy.ts`), cache diagnostics tracking and accounting (`cache-diagnostics.ts`), Anthropic server-side safety fallback (`server-fallback.ts`), deterministic client content-filter recovery (`fable-fallback.ts`), system prompt sanitization/memoization (`sanitize-memo.ts`), message-history context reconstruction (`prompt-context.ts`), request-scoped Fable 5.1 effort history (`effort-history.ts`), user-message prompt UUID and request ID lineage tracking across tool loops (`billing-lineage.ts`), lane-start request correlation (`lane-start.ts`), quota priming manager registry (`prime-manager-registry.ts`), scoped Claustrum startup admission (`custody-mode.ts`, `custody-dimensions.ts`), offline setup wizard (`setup/`), TUI sidebar widget (`tui.tsx`, rendering shared with OpenCode 2 in `tui/claude-sidebar.tsx`), TUI preferences (`tui-preferences.ts`), command modal dialogs (`tui/command-dialogs.tsx`), loopback RPC server/client for TUI IPC (`rpc/`), and TUI sidebar IPC state management (`sidebar-state.ts`)
- Depends on: `@cortexkit/anthropic-auth-core`, `@opencode-ai/plugin` (peer), `@opentui/core` + `@opentui/solid` + `solid-js` (TUI), `jsonc-parser` (TUI preferences)
- Used by: OpenCode agent (loaded as plugin + TUI plugin)

Expand Down Expand Up @@ -201,7 +201,7 @@
**SidebarState:**
- Purpose: Shared state file between OpenCode server process and TUI widget process for live quota/routing/cache display and per-session safety fallback status
- Location: `packages/opencode/src/sidebar-state.ts`
- Pattern: JSON file under `$TMPDIR`; server writes after each routing decision or quota refresh; TUI polls on interval. Supports displaying standard usage windows alongside scoped model quotas (e.g. Fable weekly limit), omitting standard five-hour and seven-day placeholders when only model-scoped limits are visible, preserving empty scoped arrays `[]` to distinguish explicit ownership from missing data, and carries a bounded set of session-keyed server-side fallback or legacy Opus/Fable recovery notices so concurrent sessions do not overwrite each other's sidebar state. Non-authoritative updates re-read the file before merging to preserve active routing sessions (`activeId`). Cross-process writes are guarded by an `mkdir` directory lock with atomic rename-claim eviction of stale locks, jittered retries, and lock-budget exhaustion skips. Writes are fenced with pre- and post-rename ownership checks; if a post-rename lock loss is detected, a single bounded locked repair restores the routing-authoritative fields into the successor's fresh state.
- Pattern: OpenCode 1: JSON file under `$TMPDIR`; server writes after each routing decision or quota refresh; TUI polls on interval. OpenCode 2: no file; the TUI polls the native RPC `sidebar` method, which projects the same shape from the native account snapshot per session (`v2/sidebar.ts`). Supports displaying standard usage windows alongside scoped model quotas (e.g. Fable weekly limit), omitting standard five-hour and seven-day placeholders when only model-scoped limits are visible, preserving empty scoped arrays `[]` to distinguish explicit ownership from missing data, and carries a bounded set of session-keyed server-side fallback or legacy Opus/Fable recovery notices so concurrent sessions do not overwrite each other's sidebar state. Non-authoritative updates re-read the file before merging to preserve active routing sessions (`activeId`). Cross-process writes are guarded by an `mkdir` directory lock with atomic rename-claim eviction of stale locks, jittered retries, and lock-budget exhaustion skips. Writes are fenced with pre- and post-rename ownership checks; if a post-rename lock loss is detected, a single bounded locked repair restores the routing-authoritative fields into the successor's fresh state.

**TuiPreferences:**
- Purpose: Comment-preserving JSONC preferences and live sidebar configuration reloads
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

This repo is a CortexKit-maintained Anthropic auth monorepo for OpenCode and Pi. The OpenCode package is a fork of the original `@ex-machina/opencode-anthropic-auth` plugin, so older entries below the initial CortexKit release are inherited from upstream package history.

## Unreleased

### Sidebar

- OpenCode 2 now shows the CLAUDE quota sidebar too, without opening `/claude`. It is the same panel as OpenCode 1 (per-account 5h, 7d and model bars with pacing, collapsed rows, routing, cache, Prime and health), drawn from one shared component in the active OpenCode 2 theme's colors and configured by the same `anthropic-auth` entry in `tui-preferences.jsonc`. The OpenCode 2 server answers it through the native RPC (`sidebar` method) from the account pool, with no file and no credential; the active account is the one that last served the sidebar's session. Turned-off vault accounts stay listed, as on OpenCode 1, and reset countdowns, reading ages and pacing keep advancing between server changes. A TUI talking to an older server shows "Waiting for quota…".
- Before an account has served the session, the collapsed sidebar no longer marks `main` as active, matching the expanded view.

## 2.1.2

- Requests no longer wait on relay or session-pin bookkeeping locks; relay settings and tokens are checked together before sending.
Expand Down
5 changes: 3 additions & 2 deletions STRUCTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ anthropic-auth/
**`packages/opencode/src/`:**
- Purpose: OpenCode plugin implementation — fetch interception, request rewriting, CLI, TUI sidebar, command dialogs
- Contains: Plugin entry point, transform pipeline, cache diagnostics, CLI, TUI widget (SolidJS), precompiled TUI loader/output, RPC server for TUI IPC, preferences management
- Key files: `index.ts` (single exported plugin factory — auth loader, command registration, background services), `request-policy.ts` (internal killswitch request classification and messaging), `transform.ts` (request body rewriting + SSE stream stripping), `custody-mode.ts` (fail-closed scoped startup admission), `custody-dimensions.ts` (route custody classification), `cache-diagnostics.ts` (cache diagnosis beta opt-in, message ID tracking, and schema v:2 record formatting), `server-fallback.ts` (Anthropic server-side safety fallback opt-in, fallback-boundary preservation, outcome detection, and completed-tool refusal continuation), `fable-fallback.ts` (session-and-source-family 10-response Opus 4.8 backstop/legacy recovery and standby cache-anchor state), `prime-manager-registry.ts` (process-wide PrimeManager registry keyed by account-storage identity), `lane-start.ts` (synthetic prompt injection and request correlation), `cli.ts` (fallback account login + relay setup), `tui.tsx` (sidebar source), `tui/entry.mjs` (host-runtime-aware compiled/raw loader), `tui/command-dialogs.tsx` (command modal dialog components), `tui-compiled/` (generated build output, shipped but git-ignored), `tui-preferences.ts` (comment-preserving JSONC preferences with directory-watch and independent polling reload paths), `sidebar-state.ts` (quota/routing, prime status, and session-keyed recovery state for TUI sidebar IPC), `sanitize-memo.ts` (system prompt sanitization memoization), `prompt-context.ts` (prompt context resolver), `effort-history.ts` (request-scoped Fable 5.1 effort timeline), `billing-lineage.ts` (user-turn prompt UUID and previous Anthropic request ID tracking across tool loops, internal correlation headers, and session invalidation)
- Key files: `index.ts` (single exported plugin factory — auth loader, command registration, background services), `request-policy.ts` (internal killswitch request classification and messaging), `transform.ts` (request body rewriting + SSE stream stripping), `custody-mode.ts` (fail-closed scoped startup admission), `custody-dimensions.ts` (route custody classification), `cache-diagnostics.ts` (cache diagnosis beta opt-in, message ID tracking, and schema v:2 record formatting), `server-fallback.ts` (Anthropic server-side safety fallback opt-in, fallback-boundary preservation, outcome detection, and completed-tool refusal continuation), `fable-fallback.ts` (session-and-source-family 10-response Opus 4.8 backstop/legacy recovery and standby cache-anchor state), `prime-manager-registry.ts` (process-wide PrimeManager registry keyed by account-storage identity), `lane-start.ts` (synthetic prompt injection and request correlation), `cli.ts` (fallback account login + relay setup), `tui.tsx` (OpenCode 1 sidebar polling and dialogs), `tui/claude-sidebar.tsx` (sidebar renderer shared by OpenCode 1 and 2, plus the OpenCode 2 mount), `tui/entry.mjs` (host-runtime-aware compiled/raw loader), `v2/sidebar.ts` (OpenCode 2 sidebar state projected from the native snapshot), `tui/command-dialogs.tsx` (command modal dialog components), `tui-compiled/` (generated build output, shipped but git-ignored), `tui-preferences.ts` (comment-preserving JSONC preferences with directory-watch and independent polling reload paths), `sidebar-state.ts` (quota/routing, prime status, and session-keyed recovery state for TUI sidebar IPC), `sanitize-memo.ts` (system prompt sanitization memoization), `prompt-context.ts` (prompt context resolver), `effort-history.ts` (request-scoped Fable 5.1 effort timeline), `billing-lineage.ts` (user-turn prompt UUID and previous Anthropic request ID tracking across tool loops, internal correlation headers, and session invalidation)

**`packages/opencode/src/setup/`:**
- Purpose: Anthropic-specific callbacks for the shared OpenCode/Pi setup runner
Expand Down Expand Up @@ -76,7 +76,8 @@ anthropic-auth/
- `packages/opencode/src/index.ts`: OpenCode plugin factory (exported as `AnthropicAuthPlugin`)
- `packages/opencode/src/cli.ts`: CLI binary entry (`opencode-anthropic-auth`)
- `packages/pi/src/index.ts`: Pi extension entry (default export function)
- `packages/opencode/src/tui.tsx`: TUI sidebar source (SolidJS component), loaded through `src/tui/entry.mjs` from precompiled output on current OpenTUI hosts
- `packages/opencode/src/tui.tsx`: OpenCode 1 TUI plugin (sidebar file polling, loopback dialogs), loaded through `src/tui/entry.mjs` from precompiled output on current OpenTUI hosts
- `packages/opencode/src/tui/claude-sidebar.tsx`: the quota sidebar renderer both generations use; OpenCode 2's `entry.mjs` setup loads the precompiled bundle when the host runtime registry is available, falls back to the TSX source otherwise, and mounts it on `sidebar.content`
- `packages/core/src/index.ts`: Shared core library entry (re-exports all modules)

**Configuration:**
Expand Down
6 changes: 3 additions & 3 deletions docs/features.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
| Quota refresh and killswitch edits | Menu actions | Summary only; no killswitch edits |
| Prime quota-window scheduler | Status and on/off actions | Status only |
| Lane start | Menu action | Not available |
| Quota sidebar | OpenCode 1 TUI plugin | Not available |
| Quota sidebar | OpenCode 1 and OpenCode 2 TUI | Not available |
| Cloudflare relay | HTTP; OpenCode 1 also supports WebSocket | HTTP or WebSocket |

[Setup](installation.md#recommended-setup) prepares the selected hosts and records which credential store they may use before sign-in. OpenCode 2 has its own [login and transport boundaries](opencode-2.md); OpenCode 1's sidebar, host-local quota-feed publisher and deterministic client safety recovery are not OpenCode 2 features.
[Setup](installation.md#recommended-setup) prepares the selected hosts and records which credential store they may use before sign-in. OpenCode 2 has its own [login and transport boundaries](opencode-2.md); OpenCode 1's host-local quota-feed publisher and deterministic client safety recovery are not OpenCode 2 features.

## What CortexKit adds over the original plugin

CortexKit adds account and request controls beyond OpenCode's or Pi's single built-in login:

- **Fallback accounts and quota-aware routing:** ordered OAuth accounts and API-key routes, minimum remaining-quota thresholds, and `sticky-balanced` session affinity. See [accounts](accounts-and-routing.md) and the [routing reference](configuration.md#fallback-accounts-and-session-affinity).
- **Cache controls:** explicit, automatic or hybrid prompt-cache placement, optional keepalive, and opt-in quota-window priming. [Cache controls](cache.md) explain the schedules and billed synthetic requests.
- **Quota visibility and limits:** per-account status, OpenCode quota refresh and killswitch thresholds, the OpenCode 1 sidebar, and its optional sanitized [quota header feed](configuration.md#quota-header-feed).
- **Quota visibility and limits:** per-account status, OpenCode quota refresh and killswitch thresholds, the quota sidebar, and OpenCode 1's optional sanitized [quota header feed](configuration.md#quota-header-feed).
- **Model controls:** adaptive reasoning summaries, effort levels, fast mode and OpenCode 1 safety fallback. See [models](models.md) and [safety fallback](request-rewriting.md#safety-fallback-opencode-1).
- **Optional custody and transport:** Claustrum is a local credential-vault service that can supply accounts instead of the local pool; a user-owned Cloudflare Worker can relay request bodies. See [vault custody](vault.md) and [transports](transports.md).
- **Claude-compatible request shaping:** identity and billing headers, final-body signing, tool replay and prompt-cache placement. See [request rewriting](request-rewriting.md#request-rewriting).
Expand Down
4 changes: 4 additions & 0 deletions docs/opencode-2.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ OpenCode 1 and 2 share the [local routing file](accounts-and-routing.md#local-st

Version-5 journals have no rollback path. Do not delete or rewrite a journal to make an older release accept it; see [upgrade and recovery](releases.md#upgrade-and-recovery).

## Quota sidebar

The TUI adds the CLAUDE quota panel to the session sidebar and polls the server's `sidebar` RPC method at the preference poll interval. See [sidebar](sidebar.md#quota-sidebar). Quota readings refresh in the background once the location has served a request; before that, persisted readings are shown with their age.

## Vault and transport boundaries

Generation-2 vault setup also leaves OpenCode 1 auth and all OpenCode databases untouched. In an enrolled host, choose **Claude vault accounts**, not the local-pool login. The vault and local-pool login methods both return only the non-secret marker; startup creates neither connection automatically. Enrollment, not account settings, selects custody. See [vault setup](vault.md#setup) and [custody gates](custody-state-machine.md#opencode-2).
Expand Down
4 changes: 3 additions & 1 deletion docs/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## Quota sidebar

OpenCode 1 can render a live quota sidebar without opening `/claude`. This is an OpenCode 1 TUI plugin, not a Pi widget or an OpenCode 2-only setup requirement.
OpenCode 1 and OpenCode 2 render a live quota sidebar without opening `/claude`. It is not a Pi widget. Both generations draw it from the same component and read the same preferences below.

OpenCode 2 needs no extra registration: the main plugin entry loads its TUI, which adds the panel to the session sidebar, takes its colors from the active OpenCode 2 theme, and asks the location's server for the session's state over the plugin's native RPC. The server builds that state from the account pool and never includes tokens, keys, provider account identities or credential material; it does carry the local route ids and labels the panel shows. The active account is the one that last served that session; before its first request none is marked active. An OpenCode 2 TUI talking to an older plugin server shows "Waiting for quota…".

OpenCode 1 loads TUI plugins from `tui.json` or `tui.jsonc` in its config directory, separately from the main `plugin` array:

Expand Down
1 change: 1 addition & 0 deletions packages/opencode/scripts/build-tui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const outputRoot = join(sourceRoot, 'tui-compiled')
const shippedSourceFiles = [
'tui.tsx',
'tui/command-dialogs.tsx',
'tui/claude-sidebar.tsx',
'sidebar-state.ts',
'tui-preferences.ts',
'rpc/rpc-client.ts',
Expand Down
11 changes: 8 additions & 3 deletions packages/opencode/src/sidebar-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -888,7 +888,10 @@ export function formatPrimeCost(value: number): string {
return value.toFixed(Math.min(6, Math.max(0, 4)))
}

export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
export function formatPrimeAccountValue(
account: PrimeSidebarAccountState,
now = Date.now(),
): {
text: string
hasError: boolean
} {
Expand All @@ -898,7 +901,7 @@ export function formatPrimeAccountValue(account: PrimeSidebarAccountState): {
if (account.lastResult === 'skipped') {
return { text: 'skip', hasError: false }
}
if (account.nextDueAt && account.nextDueAt > Date.now()) {
if (account.nextDueAt && account.nextDueAt > now) {
return { text: formatPrimeTime(account.nextDueAt), hasError: false }
}
if (account.lastPrimedAt) {
Expand Down Expand Up @@ -994,7 +997,9 @@ export function getCollapsedAccounts(
state: SidebarState,
showFallbacks = true,
) {
const activeId = resolveActiveAccount(state).id
// Before any request no account is active; do not mark main as serving.
const activeId =
state.activeId === undefined ? undefined : resolveActiveAccount(state).id
Comment thread
greptile-apps[bot] marked this conversation as resolved.
return [
{
id: 'main',
Expand Down
10 changes: 10 additions & 0 deletions packages/opencode/src/tests/opencode2-native-source.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,16 @@ describe('OC2 source over actual native services', () => {
expect(JSON.stringify(snapshot)).not.toContain('native-only-refresh')
expect(JSON.stringify(snapshot)).not.toContain(auth.accessToken)
})
test('remembers the account that served each session until the session is forgotten', async () => {
const fixture = await local()
const native = source(fixture.runtime, fixture.paths)
expect(native.servedAccount(scope.sessionID)).toBeUndefined()
expect(await native.authorizeAccount(scope, 'main')).toBeDefined()
expect(native.servedAccount(scope.sessionID)).toBe('main')
expect(native.servedAccount('ses_never_served')).toBeUndefined()
native.forgetSession?.(scope.sessionID)
expect(native.servedAccount(scope.sessionID)).toBeUndefined()
})
test('genuine header quota is account/version bound and persists through native publication', async () => {
const fixture = await local()
const native = source(fixture.runtime, fixture.paths)
Expand Down
Loading
Loading