diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-auth-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-auth-spike.mjs similarity index 95% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-auth-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-auth-spike.mjs index 70ce27228d..450755994c 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-auth-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-auth-spike.mjs @@ -8,7 +8,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-auth-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-auth-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED lane=oauth-slots" | exit 0 "ACCEPTED lane=config-dir" | exit 2 "REJECTED" @@ -45,7 +45,7 @@ if (!credential || credential.type !== "oauth" || typeof credential.access !== " const { query } = await import("@anthropic-ai/claude-agent-sdk"); const { resolveClaudeCodeExecutable, defaultExecutableDeps } = await import( - "../../../packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable.ts" + "../../../packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable.ts" ); const executable = resolveClaudeCodeExecutable(defaultExecutableDeps()); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-settings-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-settings-probe.mjs similarity index 90% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-settings-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-settings-probe.mjs index 16672df3c9..eba5a2c2f3 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-settings-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-settings-probe.mjs @@ -4,7 +4,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-settings-probe.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-settings-probe.mjs * * The sandbox must contain an OAuth credential in auth.json using the same slots * as the other claude-sdk-oauth spikes. Never prints token material. @@ -18,7 +18,7 @@ import { requireSandbox, startGuardedQuery, withTimeout, -} from "./lib/claude-sdk-oauth-spike-support.mjs"; +} from "./lib/anthropic-subscription-spike-support.mjs"; requireLiveGate(); const sandbox = requireSandbox(); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-spike.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-spike.mjs index e8682eb007..e328239707 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-spike.mjs @@ -27,7 +27,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-autocompact-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-autocompact-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED autocompact= boundary= manual_compact=" @@ -47,7 +47,7 @@ import { startGuardedQuery, userMessage, withTimeout, -} from "./lib/claude-sdk-oauth-spike-support.mjs"; +} from "./lib/anthropic-subscription-spike-support.mjs"; requireLiveGate(); const sandbox = requireSandbox(); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-fullstack-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-fullstack-probe.mjs similarity index 96% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-fullstack-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-fullstack-probe.mjs index ede5ed34ce..b66c64a528 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-fullstack-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-fullstack-probe.mjs @@ -2,7 +2,7 @@ /** * Full-stack continuity probe for the claude-sdk-oauth lane. * - * Unlike claude-sdk-oauth-registry-probe.mjs (which drives the registry APIs + * Unlike anthropic-subscription-registry-probe.mjs (which drives the registry APIs * directly), this probe drives the REAL stack: createAgentSession() -> * AgentSession.prompt() -> provider streamSimple -> resident session registry * or the flattened path. SDK query creation is @@ -16,7 +16,7 @@ * network egress are involved. * * Run with: - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-fullstack-probe.mjs --baseline + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-fullstack-probe.mjs --baseline * * Modes: * --baseline always exits 0 — the per-turn table IS the deliverable @@ -37,9 +37,9 @@ import { formatTurnTable, seedProbeAgentDir, withTimeout, -} from "./lib/claude-sdk-oauth-fullstack-support.mjs"; +} from "./lib/anthropic-subscription-fullstack-support.mjs"; import { safeDetail } from "./lib/output-safety.mjs"; -import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/claude-sdk-oauth-hermetic-env.mjs"; +import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/anthropic-subscription-hermetic-env.mjs"; const ROOT = repoRoot(); const INNER_FLAG = "SENPI_CLAUDE_SDK_FULLSTACK_PROBE_INNER"; @@ -69,7 +69,7 @@ installCleanupHooks(); // --matrix runs the todo-16 scenario matrix and exits: the loopback server, // the captured queries, and the per-turn payloads are the matrix's evidence. if (MATRIX) { - const { runContinuityMatrix } = await import("./lib/claude-sdk-oauth-matrix.mjs"); + const { runContinuityMatrix } = await import("./lib/anthropic-subscription-matrix.mjs"); process.exit(await runContinuityMatrix({ gate: !BASELINE })); } diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-headless-restart-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-headless-restart-probe.mjs similarity index 99% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-headless-restart-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-headless-restart-probe.mjs index 589610f4a6..1e1bb83cd7 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-headless-restart-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-headless-restart-probe.mjs @@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url"; import { bootHermeticStack, SOURCE_ROOT, -} from "./lib/claude-sdk-oauth-fullstack-harness.mjs"; +} from "./lib/anthropic-subscription-fullstack-harness.mjs"; import { safeDetail } from "./lib/output-safety.mjs"; const TURN_TIMEOUT_MS = 180_000; diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-native-inline-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-native-inline-spike.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-native-inline-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-native-inline-spike.mjs index be994fb6f7..265f8b045e 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-native-inline-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-native-inline-spike.mjs @@ -17,7 +17,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-native-inline-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-native-inline-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED setmodel= interrupt_receipt= continue=coherent" @@ -38,7 +38,7 @@ import { startGuardedQuery, userMessage, withTimeout, -} from "./lib/claude-sdk-oauth-spike-support.mjs"; +} from "./lib/anthropic-subscription-spike-support.mjs"; requireLiveGate(); const sandbox = requireSandbox(); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-persistent-query-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-persistent-query-spike.mjs similarity index 97% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-persistent-query-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-persistent-query-spike.mjs index cf08dfda10..f0b46b351a 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-persistent-query-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-persistent-query-spike.mjs @@ -11,7 +11,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-persistent-query-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-persistent-query-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED denial= orphan=none replay=" @@ -136,14 +136,14 @@ async function runWorker() { const { query } = await import("@anthropic-ai/claude-agent-sdk"); const { resolveClaudeCodeExecutable, defaultExecutableDeps } = await import( - "../../../packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable.ts" + "../../../packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable.ts" ); const { BUILTIN_SDK_TOOLS, HOST_CAPTURED_SDK_TOOL_MATCHER, HOST_TOOL_DENIAL_HOOKS, HOST_TOOL_EXECUTION_DENIED_MESSAGE, - } = await import("../../../packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tools.ts"); + } = await import("../../../packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tools.ts"); if (!new RegExp(`^(?:${HOST_CAPTURED_SDK_TOOL_MATCHER})$`).test("Bash")) { sendWorkerResult({ kind: "rejected", signal: "host_matcher_mismatch" }); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-reattach-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-reattach-spike.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-reattach-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-reattach-spike.mjs index 88f18a8402..325566702e 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-reattach-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-reattach-spike.mjs @@ -20,7 +20,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-reattach-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-reattach-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED resume= cache_read_ratio= cross_account= config_root=" @@ -46,8 +46,8 @@ import { requireSandbox, safeSignal, withTimeout, -} from "./lib/claude-sdk-oauth-spike-support.mjs"; -import { runTurns, TOKEN_PROMPT } from "./lib/claude-sdk-oauth-reattach-worker.mjs"; +} from "./lib/anthropic-subscription-spike-support.mjs"; +import { runTurns, TOKEN_PROMPT } from "./lib/anthropic-subscription-reattach-worker.mjs"; const WORKER_FLAG = "--reattach-worker"; const SELF = fileURLToPath(import.meta.url); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-registry-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-registry-probe.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-registry-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-registry-probe.mjs index 0f5716f8a7..3d7398a68e 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-registry-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-registry-probe.mjs @@ -3,7 +3,7 @@ * Hermetic real-surface QA for the claude-sdk-oauth resident session registry. * * Run with: - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-registry-probe.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-registry-probe.mjs * * The outer process re-execs under tsx so this JavaScript probe can import the * live TypeScript implementation. The inner process drives the registry APIs @@ -56,7 +56,7 @@ const IMPLEMENTATION_DIR = join( ); const importImplementation = (file) => import(pathToFileURL(join(IMPLEMENTATION_DIR, file)).href); -const [{ ClaudeSdkOauthSessionRegistry }, { submitSessionTurn }, sync] = await Promise.all([ +const [{ AnthropicSubscriptionSessionRegistry }, { submitSessionTurn }, sync] = await Promise.all([ importImplementation("session-registry.ts"), importImplementation("session-registry-pump.ts"), importImplementation("session-sync.ts"), @@ -165,7 +165,7 @@ function fact(label, pass, detail) { const authGuard = guardRealAuth(); const box = makeSandbox("claude-sdk-registry-probe"); -const registry = new ClaudeSdkOauthSessionRegistry(); +const registry = new AnthropicSubscriptionSessionRegistry(); const providerRequests = []; const localControlRequests = []; const unexpectedRequests = []; diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-stall-retry-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-stream-stall-retry-probe.mjs similarity index 95% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-stall-retry-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-stream-stall-retry-probe.mjs index b3e9487204..7b5a60a5ee 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-stall-retry-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-stream-stall-retry-probe.mjs @@ -7,8 +7,8 @@ import { spawn } from "node:child_process"; import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; -import { bootHermeticStack, SOURCE_ROOT } from "./lib/claude-sdk-oauth-fullstack-harness.mjs"; -import { classifyPayload } from "./lib/claude-sdk-oauth-fullstack-support.mjs"; +import { bootHermeticStack, SOURCE_ROOT } from "./lib/anthropic-subscription-fullstack-harness.mjs"; +import { classifyPayload } from "./lib/anthropic-subscription-fullstack-support.mjs"; import { safeDetail } from "./lib/output-safety.mjs"; const TURN_TIMEOUT_MS = 120_000; @@ -190,7 +190,7 @@ if (evidenceSlug) { writeFileSync(join(directory, "probe-resume.json"), `${JSON.stringify(summary, null, 2)}\n`); writeFileSync( join(directory, "probe-resume.log"), - `command: node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-stall-retry-probe.mjs --evidence ${evidenceSlug}\n` + + `command: node .agents/skills/senpi-qa/scripts/anthropic-subscription-stream-stall-retry-probe.mjs --evidence ${evidenceSlug}\n` + `cleanup: ${cleanup.join("; ")}\n\n${JSON.stringify(summary, null, 2)}\n`, ); process.stdout.write(`EVIDENCE ${join(directory, "probe-resume.log")}\n`); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-start-knob-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-stream-start-knob-probe.mjs similarity index 96% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-start-knob-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-stream-start-knob-probe.mjs index a6620d5538..913a5c94f2 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-start-knob-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-stream-start-knob-probe.mjs @@ -9,7 +9,7 @@ * Anthropic server that HOLDS its response past the configured 2s guard. No * credentials, no network egress. * - * Usage: bun .agents/skills/senpi-qa/scripts/claude-sdk-oauth-stream-start-knob-probe.mjs [--evidence ] + * Usage: bun .agents/skills/senpi-qa/scripts/anthropic-subscription-stream-start-knob-probe.mjs [--evidence ] */ import { createServer } from "node:http"; @@ -17,8 +17,8 @@ import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { guardRealAuth, installCleanupHooks, makeSandbox, repoRoot, track } from "./lib/common.mjs"; -import { loopbackSseBody, seedProbeAgentDir } from "./lib/claude-sdk-oauth-fullstack-support.mjs"; -import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/claude-sdk-oauth-hermetic-env.mjs"; +import { loopbackSseBody, seedProbeAgentDir } from "./lib/anthropic-subscription-fullstack-support.mjs"; +import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/anthropic-subscription-hermetic-env.mjs"; import { withTimeout } from "./lib/with-timeout.mjs"; const ROOT = repoRoot(); diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-sysprompt-spike.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-sysprompt-spike.mjs similarity index 94% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-sysprompt-spike.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-sysprompt-spike.mjs index 1ac8ba88dc..7edf5970fb 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-sysprompt-spike.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-sysprompt-spike.mjs @@ -7,7 +7,7 @@ * * Usage: * SENPI_LIVE_CLAUDE_SDK_OAUTH=1 SENPI_CODING_AGENT_DIR= \ - * node .agents/skills/senpi-qa/scripts/claude-sdk-oauth-sysprompt-spike.mjs + * node .agents/skills/senpi-qa/scripts/anthropic-subscription-sysprompt-spike.mjs * * Outcomes (final line): * exit 0 "ACCEPTED sysprompt=plain-string" @@ -44,7 +44,7 @@ if (!credential || credential.type !== "oauth" || typeof credential.access !== " const { query } = await import("@anthropic-ai/claude-agent-sdk"); const { resolveClaudeCodeExecutable, defaultExecutableDeps } = await import( - "../../../packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable.ts" + "../../../packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable.ts" ); function managedEnvironment(access) { diff --git a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-toolless-compact-probe.mjs b/.agents/skills/senpi-qa/scripts/anthropic-subscription-toolless-compact-probe.mjs similarity index 96% rename from .agents/skills/senpi-qa/scripts/claude-sdk-oauth-toolless-compact-probe.mjs rename to .agents/skills/senpi-qa/scripts/anthropic-subscription-toolless-compact-probe.mjs index 7b9e950d0d..4190ee1652 100644 --- a/.agents/skills/senpi-qa/scripts/claude-sdk-oauth-toolless-compact-probe.mjs +++ b/.agents/skills/senpi-qa/scripts/anthropic-subscription-toolless-compact-probe.mjs @@ -18,16 +18,16 @@ * compaction still applied through the deterministic * fallback (details.origin === "required-compaction-recovery"). * - * Usage: bun .agents/skills/senpi-qa/scripts/claude-sdk-oauth-toolless-compact-probe.mjs [--mode toolless|always-tool] [--evidence ] + * Usage: bun .agents/skills/senpi-qa/scripts/anthropic-subscription-toolless-compact-probe.mjs [--mode toolless|always-tool] [--evidence ] */ import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { guardRealAuth, installCleanupHooks, makeSandbox, repoRoot, track } from "./lib/common.mjs"; -import { seedProbeAgentDir } from "./lib/claude-sdk-oauth-fullstack-support.mjs"; -import { startToollessCompactServer } from "./lib/claude-sdk-oauth-toolless-compact-server.mjs"; -import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/claude-sdk-oauth-hermetic-env.mjs"; +import { seedProbeAgentDir } from "./lib/anthropic-subscription-fullstack-support.mjs"; +import { startToollessCompactServer } from "./lib/anthropic-subscription-toolless-compact-server.mjs"; +import { applyHermeticEnvironment, assertHermeticEnvironment } from "./lib/anthropic-subscription-hermetic-env.mjs"; import { withTimeout } from "./lib/with-timeout.mjs"; const ROOT = repoRoot(); diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-harness.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-harness.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-harness.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-harness.mjs index 07eac9c0e0..0b65f438de 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-harness.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-harness.mjs @@ -13,8 +13,8 @@ import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { pathToFileURL } from "node:url"; import { guardRealAuth, makeSandbox, repoRoot, track } from "./common.mjs"; -import { loopbackSseBody, seedProbeAgentDir } from "./claude-sdk-oauth-fullstack-support.mjs"; -import { applyHermeticEnvironment, assertHermeticEnvironment } from "./claude-sdk-oauth-hermetic-env.mjs"; +import { loopbackSseBody, seedProbeAgentDir } from "./anthropic-subscription-fullstack-support.mjs"; +import { applyHermeticEnvironment, assertHermeticEnvironment } from "./anthropic-subscription-hermetic-env.mjs"; const ROOT = repoRoot(); export const SOURCE_ROOT = join(ROOT, "packages", "coding-agent", "src"); diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-support.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-support.mjs similarity index 99% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-support.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-support.mjs index c55bdf8f44..d0d8c30c92 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-fullstack-support.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-fullstack-support.mjs @@ -1,5 +1,5 @@ /** - * Shared helpers for claude-sdk-oauth-fullstack-probe.mjs. + * Shared helpers for anthropic-subscription-fullstack-probe.mjs. * * Kept out of the probe so each script stays under the 250 pure-LOC ceiling. */ diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-hermetic-env.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-hermetic-env.mjs similarity index 100% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-hermetic-env.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-hermetic-env.mjs diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-assert.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-assert.mjs similarity index 100% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-assert.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-assert.mjs diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-constants.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-constants.mjs similarity index 100% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-constants.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-constants.mjs diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-phases.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-phases.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-phases.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-phases.mjs index b791979731..e76d071ede 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-phases.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-phases.mjs @@ -10,7 +10,7 @@ * SDK boundary. */ -import { SESSION_REGISTRY_IDLE_TTL_MS } from "./claude-sdk-oauth-matrix-constants.mjs"; +import { SESSION_REGISTRY_IDLE_TTL_MS } from "./anthropic-subscription-matrix-constants.mjs"; /** * Phase (a): a plain 6-turn conversation must cost EXACTLY ONE SDK query in diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-run.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-run.mjs similarity index 97% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-run.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-run.mjs index c2ebb860d5..d17d1c81d6 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix-run.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix-run.mjs @@ -5,8 +5,8 @@ * (injected clock, registry wipe, branch navigation, mid-turn abort). */ -import { classifyPayload, withTimeout } from "./claude-sdk-oauth-fullstack-support.mjs"; -import { MATRIX_TURN_TIMEOUT_MS } from "./claude-sdk-oauth-matrix-constants.mjs"; +import { classifyPayload, withTimeout } from "./anthropic-subscription-fullstack-support.mjs"; +import { MATRIX_TURN_TIMEOUT_MS } from "./anthropic-subscription-matrix-constants.mjs"; /** Yields until the macrotask queue is empty — a drain, never a timed wait. */ function drainMacrotasks(rounds = 3) { diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix.mjs similarity index 93% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix.mjs index 71e43f1807..407b5ad193 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-matrix.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-matrix.mjs @@ -10,11 +10,11 @@ import { pathToFileURL } from "node:url"; import { join } from "node:path"; import { safeDetail } from "./output-safety.mjs"; -import { bootHermeticStack, resolveMatrixModels, SOURCE_ROOT } from "./claude-sdk-oauth-fullstack-harness.mjs"; -import { assertMatrix } from "./claude-sdk-oauth-matrix-assert.mjs"; -import { VERDICT_LABEL } from "./claude-sdk-oauth-matrix-constants.mjs"; -import { MATRIX_PHASES } from "./claude-sdk-oauth-matrix-phases.mjs"; -import { MatrixRun } from "./claude-sdk-oauth-matrix-run.mjs"; +import { bootHermeticStack, resolveMatrixModels, SOURCE_ROOT } from "./anthropic-subscription-fullstack-harness.mjs"; +import { assertMatrix } from "./anthropic-subscription-matrix-assert.mjs"; +import { VERDICT_LABEL } from "./anthropic-subscription-matrix-constants.mjs"; +import { MATRIX_PHASES } from "./anthropic-subscription-matrix-phases.mjs"; +import { MatrixRun } from "./anthropic-subscription-matrix-run.mjs"; const MODEL_ID = "claude-haiku-4-5"; /** Phase (g) is the only fork boundary that runs; (f)/(i) are skipped, so add none. */ diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-reattach-worker.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-reattach-worker.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-reattach-worker.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-reattach-worker.mjs index 4e4cf1bec3..1c13d8a279 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-reattach-worker.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-reattach-worker.mjs @@ -1,5 +1,5 @@ /** - * Worker half of claude-sdk-oauth-reattach-spike.mjs. + * Worker half of anthropic-subscription-reattach-spike.mjs. * * Runs one streaming-input query (optionally with `resume: ` and/or * an env-scoped CLAUDE_CONFIG_DIR), plays the requested prompts, and reports @@ -18,7 +18,7 @@ import { managedEnvironment, userMessage, withTimeout, -} from "./claude-sdk-oauth-spike-support.mjs"; +} from "./anthropic-subscription-spike-support.mjs"; export const TOKEN_PROMPT = (token) => `Remember this token for later: ${token}. Reply with exactly: ACK`; diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-spike-credentials.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-spike-credentials.mjs similarity index 100% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-spike-credentials.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-spike-credentials.mjs diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-spike-support.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-spike-support.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-spike-support.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-spike-support.mjs index af0c5938c0..711693305f 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-spike-support.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-spike-support.mjs @@ -34,10 +34,10 @@ export function requireSandbox() { process.exit(2); } -// Credential loading lives in claude-sdk-oauth-spike-credentials.mjs (split +// Credential loading lives in anthropic-subscription-spike-credentials.mjs (split // out so this module stays under the 250 pure-LOC ceiling); re-exported so // existing spike imports keep working. -export { loadCredential, loadCredentialStrict } from "./claude-sdk-oauth-spike-credentials.mjs"; +export { loadCredential, loadCredentialStrict } from "./anthropic-subscription-spike-credentials.mjs"; /** Sanitize any signal/reason into the fixed terminal-line vocabulary shape. */ export function safeSignal(value) { diff --git a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-toolless-compact-server.mjs b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-toolless-compact-server.mjs similarity index 98% rename from .agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-toolless-compact-server.mjs rename to .agents/skills/senpi-qa/scripts/lib/anthropic-subscription-toolless-compact-server.mjs index fefa48fff2..db4185679f 100644 --- a/.agents/skills/senpi-qa/scripts/lib/claude-sdk-oauth-toolless-compact-server.mjs +++ b/.agents/skills/senpi-qa/scripts/lib/anthropic-subscription-toolless-compact-server.mjs @@ -7,7 +7,7 @@ */ import { createServer } from "node:http"; -import { loopbackSseBody } from "./claude-sdk-oauth-fullstack-support.mjs"; +import { loopbackSseBody } from "./anthropic-subscription-fullstack-support.mjs"; import { track } from "./common.mjs"; export function toolUseSseBody(toolName, sequence, modelId) { diff --git a/.agents/skills/senpi-qa/scripts/rpc-drive.mjs b/.agents/skills/senpi-qa/scripts/rpc-drive.mjs index 155ec75b09..a571f657d6 100644 --- a/.agents/skills/senpi-qa/scripts/rpc-drive.mjs +++ b/.agents/skills/senpi-qa/scripts/rpc-drive.mjs @@ -76,7 +76,7 @@ async function selfTest() { process.exit(passed ? 0 : 1); } -async function driveScenarioClaudeSdkOauthAccounts() { +async function driveScenarioAnthropicSubscriptionAccounts() { installCleanupHooks(); const guard = guardRealAuth(); const checks = createChecks("rpc-drive.mjs --scenario claude-sdk-oauth-accounts"); @@ -256,7 +256,7 @@ if (argv[0] === "--self-test") { } else if (argv[0] === "--state") { driveState(); } else if (argv[0] === "--scenario" && argv[1] === "claude-sdk-oauth-accounts") { - driveScenarioClaudeSdkOauthAccounts().catch((e) => { + driveScenarioAnthropicSubscriptionAccounts().catch((e) => { process.stderr.write(`${e instanceof Error ? e.stack : String(e)}\n`); process.exit(1); }); diff --git a/.omo/plans/issue-589-websocket-fallback-recovery.md b/.omo/plans/issue-589-websocket-fallback-recovery.md index 330328acc4..fd011be813 100644 --- a/.omo/plans/issue-589-websocket-fallback-recovery.md +++ b/.omo/plans/issue-589-websocket-fallback-recovery.md @@ -12,7 +12,7 @@ that never retries an already-started response. failures moved the session to SSE. - `websocketSseFallbackSessions` is currently a process-lifetime `Set`. Once added, a session has no production recovery path. -- `closeOpenAICodexWebSocketSessions` closes sockets but leaves fallback and +- `closeChatGptSubscriptionWebSocketSessions` closes sockets but leaves fallback and debug state behind. - PR #597 fixed the SSE request-affinity tuple. This plan fixes the remaining client-controlled exposure: permanent degradation after a transient failure. @@ -41,7 +41,7 @@ RED: current permanent fallback sends the post-cooldown request through SSE. ### 2. Cleanup clears degradation state -Trigger fallback, call `closeOpenAICodexWebSocketSessions(sessionId)`, then +Trigger fallback, call `closeChatGptSubscriptionWebSocketSessions(sessionId)`, then issue a fresh same-session request with a working WebSocket. PASS: WebSocket is attempted immediately and stale fallback/debug state is diff --git a/changes.md b/changes.md index 2b9977cd98..8b28deae91 100644 --- a/changes.md +++ b/changes.md @@ -109,10 +109,10 @@ ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts`: new `upsertAccount()` — a same-name slot is replaced in place (fresh token material, block stamps cleared, `displayName` preserved); unknown names still append. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts`: a re-login now targets an existing slot instead of minting `account-N+1` — a lone slot or the pool's one `auth_error`-blocked slot is refreshed in place; anything else stays append-only unless the user types an existing name, so a blank or headless re-login never overwrites the newest working slot in a multi-account pool. The Anthropic import is now a move: accepting it removes the grant from the `anthropic` provider so two stores never refresh one single-use token. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts`: wires `removeAnthropicCredential` through the locked auth.json backend. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/{affinity,guidance,stream-guidance}.ts`: `AllAccountsBlockedError` carries the dominant block reason and the all-blocked guidance names an authentication failure explicitly, so the outer credential-pool classifier maps it to `auth_error` instead of laundering it into a rate-limit cooldown via the generic "(rate limit or auth errors)" wording. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts`: new `upsertAccount()` — a same-name slot is replaced in place (fresh token material, block stamps cleared, `displayName` preserved); unknown names still append. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts`: a re-login now targets an existing slot instead of minting `account-N+1` — a lone slot or the pool's one `auth_error`-blocked slot is refreshed in place; anything else stays append-only unless the user types an existing name, so a blank or headless re-login never overwrites the newest working slot in a multi-account pool. The Anthropic import is now a move: accepting it removes the grant from the `anthropic` provider so two stores never refresh one single-use token. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts`: wires `removeAnthropicCredential` through the locked auth.json backend. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/{affinity,guidance,stream-guidance}.ts`: `AllAccountsBlockedError` carries the dominant block reason and the all-blocked guidance names an authentication failure explicitly, so the outer credential-pool classifier maps it to `auth_error` instead of laundering it into a rate-limit cooldown via the generic "(rate limit or auth errors)" wording. - `packages/coding-agent/src/core/credential-pool/{state-store,rotation-stream}.ts` and `packages/coding-agent/src/core/credential-accounts.ts`: stored-lane sidecar health is bound to a credential revision (HMAC over the installation key and slot material, never raw material) — the stored-lane twin of the env revision rule — so a re-login or token refresh retires the block the old material earned. Legacy rows without a revision are retired on first read. - Tests: new `test/claude-sdk-oauth-login-refresh.test.ts` (refresh matrix, import move, dominant reason) and `test/credential-pool-stored-revision.test.ts` (legacy/foreign/current revision, revision stamping); `test/credential-error-taxonomy.test.ts` gains the guidance→classifier composition cases; `test/credential-accounts.test.ts` and `test/model-runtime-credential-rotation.test.ts` fixtures now stamp the matching revision for blocks that must apply. @@ -127,7 +127,7 @@ ### Expected merge conflict zones -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts` (login naming and the import branch). +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts` (login naming and the import branch). - `packages/coding-agent/src/core/credential-pool/rotation-stream.ts` (stored-lane listing and `persistBlock`). ## Type-check the qa scripts (2026-09-16) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index 86101f8c40..42bbeef5c3 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -8,6 +8,8 @@ ### Changed +- Internal module and symbol names now follow the subscription provider rename: `openai-codex.ts` -> `chatgpt-subscription.ts` (provider, OAuth flow and auth utils), `openaiCodexOAuth` -> `chatgptSubscriptionOAuth`, and the rest of the identifier family likewise; the model-data shard and manifest entry renamed with them. The wire api id `openai-codex-responses`, its adapter files and every persisted token are unchanged. ([#1989](https://github.com/code-yeongyu/senpi/issues/1989)) + - Pooled-credential slot repair keeps recognizing the managed sentinel after the `claude-sdk-oauth` -> `anthropic-subscription` provider rename: matchers accept both `anthropic-subscription-managed` and the legacy `claude-sdk-oauth-managed` material that existing stored credentials carry verbatim. ([#1989](https://github.com/code-yeongyu/senpi/issues/1989)) ### Fixed @@ -3608,7 +3610,7 @@ ### Fixed -- Fixed Bun runtime detection for dynamic imports in browser-compatible modules (stream.ts, openai-codex-responses.ts, openai-codex.ts) ([#922](https://github.com/badlogic/pi-mono/pull/922) by [@dannote](https://github.com/dannote)) +- Fixed Bun runtime detection for dynamic imports in browser-compatible modules (stream.ts, openai-codex-responses.ts, chatgpt-subscription.ts) ([#922](https://github.com/badlogic/pi-mono/pull/922) by [@dannote](https://github.com/dannote)) - Fixed streaming functions to use `model.api` instead of hardcoded API types - Fixed Google providers to default tool call arguments to an empty object when omitted - Fixed OpenAI Responses streaming to handle `arguments.done` events on OpenAI-compatible endpoints ([#917](https://github.com/badlogic/pi-mono/pull/917) by [@williballenthin](https://github.com/williballenthin)) @@ -3621,7 +3623,7 @@ ### Added - Added `headers` option to `StreamOptions` for custom HTTP headers in API requests. Supported by all providers except Amazon Bedrock (which uses AWS SDK auth). Headers are merged with provider defaults and `model.headers`, with `options.headers` taking precedence. -- Added `originator` option to `loginOpenAICodex()` for custom OAuth client identification +- Added `originator` option to `loginChatGptSubscription()` for custom OAuth client identification - Browser compatibility for pi-ai: replaced top-level Node.js imports with dynamic imports for browser environments ([#873](https://github.com/badlogic/pi-mono/issues/873)) ### Fixed diff --git a/packages/ai/changes.md b/packages/ai/changes.md index 4befda3363..f638a0da84 100644 --- a/packages/ai/changes.md +++ b/packages/ai/changes.md @@ -364,7 +364,7 @@ The generator produces the committed catalog shards that ship inside this packag ### What changed -- `packages/ai/scripts/generate-models.ts`: `toOpenAiInputCap` maps the documented OpenAI window tiers to their prompt budgets for provider `openai` (400,000 -> 272,000; 1,050,000 -> 922,000 when `maxTokens` is 128,000), `GPT_6_ASTRA_DEFAULT_CONTEXT_WINDOW` and the Azure flagship overrides use the 922,000 cap. Regenerated `packages/ai/src/providers/data/openai.json`, `packages/ai/src/providers/data/openai-codex.json`, `packages/ai/src/providers/data/azure-openai-responses.json`, `packages/ai/src/providers/data/.manifest.json` (the same run picked up one OpenRouter price refresh). +- `packages/ai/scripts/generate-models.ts`: `toOpenAiInputCap` maps the documented OpenAI window tiers to their prompt budgets for provider `openai` (400,000 -> 272,000; 1,050,000 -> 922,000 when `maxTokens` is 128,000), `GPT_6_ASTRA_DEFAULT_CONTEXT_WINDOW` and the Azure flagship overrides use the 922,000 cap. Regenerated `packages/ai/src/providers/data/openai.json`, `packages/ai/src/providers/data/chatgpt-subscription.json`, `packages/ai/src/providers/data/azure-openai-responses.json`, `packages/ai/src/providers/data/.manifest.json` (the same run picked up one OpenRouter price refresh). ### Why @@ -521,7 +521,7 @@ The generator produces the committed catalog shards that ship inside this packag - `packages/ai/scripts/generate-models.ts`: hand-added `gpt-6-astra` entries for the `openai` and `openai-codex` providers (published pricing 10/50/1/12.5 per MTok with the >272k long-context tiers, 272k default context, 128k output, text+image input, `thinkingLevelMap` with `off`/`minimal` null and low through high, with xhigh/max merged by `supportsOpenAiXhigh`/`supportsOpenAiMax`); the id joins the tool-search, additional-tools, short-context-cap, long-context-pricing, and Priority `-fast` sets; a post-metadata override keeps `off`/`minimal` unavailable. - `packages/ai/src/models.ts`: `XHIGH_MODEL_IDS` gains `gpt-6-astra`; the sol-only max-effort family check is generalized to `OPENAI_MAX_MODEL_IDS` (`gpt-5.6-sol`, `gpt-6-astra`). -- Regenerated `packages/ai/src/providers/data/openai.json`, `packages/ai/src/providers/data/openai-codex.json`, and `packages/ai/src/providers/data/.manifest.json` with only the Astra entries (plus their `-fast` variants) changing. +- Regenerated `packages/ai/src/providers/data/openai.json`, `packages/ai/src/providers/data/chatgpt-subscription.json`, and `packages/ai/src/providers/data/.manifest.json` with only the Astra entries (plus their `-fast` variants) changing. - `packages/ai/test/gpt-6-astra-catalog.test.ts`: catalog entries, pricing tiers, context limits, thinking levels, xhigh/max support, and `-fast` variants. ### Why @@ -1164,7 +1164,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - `src/context-provenance.ts`: added request-local, non-enumerable message/item provenance tokens. - `src/api/openai-responses-shared.ts`: preserves those tokens while converting messages to Responses input items. - `src/types.ts` and `src/index.ts`: expose the typed provenance helpers needed by coding-agent's replay boundary. -- `src/utils/openai-codex-auth.ts`: centralizes browser-safe ChatGPT account-ID extraction so normal Codex requests +- `src/utils/chatgpt-subscription-auth.ts`: centralizes browser-safe ChatGPT account-ID extraction so normal Codex requests and remote compaction canonicalize the same wire tenant across bearer-token refreshes. ### Why @@ -1181,7 +1181,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - `src/api/openai-responses-shared.ts` - `src/index.ts` - `src/types.ts` -- `src/utils/openai-codex-auth.ts` +- `src/utils/chatgpt-subscription-auth.ts` ### Expected merge conflict zones @@ -1320,7 +1320,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - `test/fireworks-models.test.ts` - `test/github-copilot-oauth.test.ts` - `test/oauth-device-code.test.ts` -- `test/openai-codex-stream.test.ts` +- `test/chatgpt-subscription-stream.test.ts` ### Expected merge conflict zones diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index 3b760aa262..f63e794556 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -404,7 +404,7 @@ const OPENAI_TOOL_SEARCH_MODEL_IDS = new Set([ // its Responses Lite GPT-5.6 models. // https://developers.openai.com/api/docs/guides/tools-tool-search#add-tools-at-a-specific-point-in-the-input const OPENAI_ADDITIONAL_TOOLS_MODEL_IDS = OPENAI_TOOL_SEARCH_MODEL_IDS; -const OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS = new Set(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"]); +const CHATGPT_SUBSCRIPTION_ADDITIONAL_TOOLS_MODEL_IDS = new Set(["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna", "gpt-6-astra"]); const OPENAI_LONG_CONTEXT_INPUT_THRESHOLD = 272000; // OpenAI budgets input and output separately: the Responses API rejects a request with // `context_too_large` once the prompt alone exceeds (documented context window - max output), @@ -532,7 +532,7 @@ const OPENAI_PRIORITY_TIER_MODEL_IDS = new Set([ // OpenAI priority list but restricted to GPT-5.6 reasoning models that ship on // the Codex Responses API; older Codex-only SKUs (gpt-5.3-codex-spark) are // Priority-ineligible. -const OPENAI_CODEX_PRIORITY_TIER_MODEL_IDS = new Set([ +const CHATGPT_SUBSCRIPTION_PRIORITY_TIER_MODEL_IDS = new Set([ "gpt-6-astra", "gpt-5.6-sol", "gpt-5.6-terra", @@ -1005,11 +1005,11 @@ function applyOpenAIGrammarToolCompatMetadata(model: Model): void { function applyOpenAIToolSearchMetadata(model: Model): void { const isOpenAIResponses = model.provider === "openai" && model.api === "openai-responses"; - const isOpenAICodex = model.provider === "chatgpt-subscription" && model.api === "openai-codex-responses"; - if (!(isOpenAIResponses || isOpenAICodex) || !OPENAI_TOOL_SEARCH_MODEL_IDS.has(model.id)) return; + const isChatGptSubscription = model.provider === "chatgpt-subscription" && model.api === "openai-codex-responses"; + if (!(isOpenAIResponses || isChatGptSubscription) || !OPENAI_TOOL_SEARCH_MODEL_IDS.has(model.id)) return; const supportsAdditionalTools = (isOpenAIResponses && OPENAI_ADDITIONAL_TOOLS_MODEL_IDS.has(model.id)) || - (isOpenAICodex && OPENAI_CODEX_ADDITIONAL_TOOLS_MODEL_IDS.has(model.id)); + (isChatGptSubscription && CHATGPT_SUBSCRIPTION_ADDITIONAL_TOOLS_MODEL_IDS.has(model.id)); model.compat = { ...(model.compat as OpenAIResponsesCompat | undefined), ...(supportsAdditionalTools ? { supportsAdditionalTools: true } : {}), @@ -3496,7 +3496,7 @@ async function generateModels() { } const codexFastVariants: Model[] = []; for (const model of allModels) { - if (model.provider !== "chatgpt-subscription" || !OPENAI_CODEX_PRIORITY_TIER_MODEL_IDS.has(model.id)) continue; + if (model.provider !== "chatgpt-subscription" || !CHATGPT_SUBSCRIPTION_PRIORITY_TIER_MODEL_IDS.has(model.id)) continue; codexFastVariants.push({ ...model, id: `${model.id}-fast`, diff --git a/packages/ai/src/api/openai-codex-responses.ts b/packages/ai/src/api/openai-codex-responses.ts index f2ac81f16e..4df63ce3c0 100644 --- a/packages/ai/src/api/openai-codex-responses.ts +++ b/packages/ai/src/api/openai-codex-responses.ts @@ -37,6 +37,7 @@ import type { Usage, } from "../types.ts"; import { combineAbortSignals } from "../utils/abort-signals.ts"; +import { extractChatGptSubscriptionAccountId } from "../utils/chatgpt-subscription-auth.ts"; import { splitDeferredTools } from "../utils/deferred-tools.ts"; import { appendAssistantMessageDiagnostic, @@ -47,21 +48,20 @@ import { formatProviderError, normalizeProviderError } from "../utils/error-body import { AssistantMessageEventStream } from "../utils/event-stream.ts"; import { headersToRecord } from "../utils/headers.ts"; import { resolveHttpProxyUrlForTarget } from "../utils/node-http-proxy.ts"; -import { extractOpenAiCodexAccountId } from "../utils/openai-codex-auth.ts"; import { appendRetryAfterMsMarker, extract429RetryAfterMs } from "../utils/retry-hint.ts"; import { uuidv7 } from "../utils/uuid.ts"; import { createGrammarToolInputProperties } from "./constrained-sampling.ts"; import { + type ChatGptSubscriptionWebSocketDebugStats, clearWebSocketFallbackState, getOrCreateWebSocketDebugStats, getWebSocketDebugStats, isWebSocketSseFallbackActive, - type OpenAICodexWebSocketDebugStats, recordWebSocketFailure, recordWebSocketSseFallback, } from "./openai-codex-responses/fallback-state.ts"; import { buildCodexReasoning, type CodexReasoningSummaryInput } from "./openai-codex-responses/reasoning.ts"; -import { applyOpenAICodexCacheAffinityHeaders, clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts"; +import { applyChatGptSubscriptionCacheAffinityHeaders, clampOpenAIPromptCacheKey } from "./openai-prompt-cache.ts"; import { convertResponsesMessages, convertResponsesTools, processResponsesStream } from "./openai-responses-shared.ts"; import { applyExtraBody, @@ -940,19 +940,21 @@ interface CachedWebSocketConnection { continuation?: CachedWebSocketContinuationState; } -export type { OpenAICodexWebSocketDebugStats } from "./openai-codex-responses/fallback-state.ts"; +export type { ChatGptSubscriptionWebSocketDebugStats } from "./openai-codex-responses/fallback-state.ts"; const websocketSessionCache = new Map>(); -export function getOpenAICodexWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined { +export function getChatGptSubscriptionWebSocketDebugStats( + sessionId: string, +): ChatGptSubscriptionWebSocketDebugStats | undefined { return getWebSocketDebugStats(sessionId); } -export function resetOpenAICodexWebSocketDebugStats(sessionId?: string): void { +export function resetChatGptSubscriptionWebSocketDebugStats(sessionId?: string): void { clearWebSocketFallbackState(sessionId); } -export function closeOpenAICodexWebSocketSessions(sessionId?: string): void { +export function closeChatGptSubscriptionWebSocketSessions(sessionId?: string): void { const closeEntry = (entry: CachedWebSocketConnection) => { unparkSessionWebSocket(entry); closeWebSocketSilently(entry.socket, 1000, "debug_close"); @@ -970,7 +972,7 @@ export function closeOpenAICodexWebSocketSessions(sessionId?: string): void { clearWebSocketFallbackState(); } -registerSessionResourceCleanup(closeOpenAICodexWebSocketSessions); +registerSessionResourceCleanup(closeChatGptSubscriptionWebSocketSessions); type WebSocketConstructor = new ( url: string, @@ -1484,7 +1486,7 @@ function buildCachedWebSocketRequestBody(entry: CachedWebSocketConnection, body: } function recordWebSocketRequestStats( - stats: OpenAICodexWebSocketDebugStats | undefined, + stats: ChatGptSubscriptionWebSocketDebugStats | undefined, requestBody: RequestBody, reused: boolean, useCachedContext: boolean, @@ -1651,7 +1653,7 @@ async function parseErrorResponse(response: Response): Promise<{ message: string // ============================================================================ function extractAccountId(token: string): string | undefined { - return extractOpenAiCodexAccountId(token); + return extractChatGptSubscriptionAccountId(token); } function buildBaseCodexHeaders( @@ -1693,7 +1695,7 @@ function buildSSEHeaders( headers.set("accept", "text/event-stream"); headers.set("content-type", "application/json"); - applyOpenAICodexCacheAffinityHeaders(headers, sessionId); + applyChatGptSubscriptionCacheAffinityHeaders(headers, sessionId); return headers; } @@ -1711,6 +1713,6 @@ function buildWebSocketHeaders( headers.delete("OpenAI-Beta"); headers.delete("openai-beta"); headers.set("OpenAI-Beta", OPENAI_BETA_RESPONSES_WEBSOCKETS); - applyOpenAICodexCacheAffinityHeaders(headers, requestId); + applyChatGptSubscriptionCacheAffinityHeaders(headers, requestId); return headers; } diff --git a/packages/ai/src/api/openai-codex-responses/fallback-state.ts b/packages/ai/src/api/openai-codex-responses/fallback-state.ts index 6c2fda6028..ef1a820d2b 100644 --- a/packages/ai/src/api/openai-codex-responses/fallback-state.ts +++ b/packages/ai/src/api/openai-codex-responses/fallback-state.ts @@ -2,7 +2,7 @@ import { formatThrownValue } from "../../utils/diagnostics.ts"; export const CODEX_WEBSOCKET_FALLBACK_COOLDOWN_MS = 60_000; -export interface OpenAICodexWebSocketDebugStats { +export interface ChatGptSubscriptionWebSocketDebugStats { requests: number; connectionsCreated: number; connectionsReused: number; @@ -19,10 +19,10 @@ export interface OpenAICodexWebSocketDebugStats { lastWebSocketError?: string; } -const websocketDebugStats = new Map(); +const websocketDebugStats = new Map(); const websocketSseFallbackUntil = new Map(); -export function getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats { +export function getOrCreateWebSocketDebugStats(sessionId: string): ChatGptSubscriptionWebSocketDebugStats { let stats = websocketDebugStats.get(sessionId); if (!stats) { stats = { @@ -42,7 +42,7 @@ export function getOrCreateWebSocketDebugStats(sessionId: string): OpenAICodexWe return stats; } -export function getWebSocketDebugStats(sessionId: string): OpenAICodexWebSocketDebugStats | undefined { +export function getWebSocketDebugStats(sessionId: string): ChatGptSubscriptionWebSocketDebugStats | undefined { const stats = websocketDebugStats.get(sessionId); return stats ? { ...stats } : undefined; } diff --git a/packages/ai/src/api/openai-prompt-cache.ts b/packages/ai/src/api/openai-prompt-cache.ts index dd29246174..54d1ba3ee5 100644 --- a/packages/ai/src/api/openai-prompt-cache.ts +++ b/packages/ai/src/api/openai-prompt-cache.ts @@ -7,7 +7,7 @@ export function clampOpenAIPromptCacheKey(key: string | undefined): string | und return chars.slice(0, OPENAI_PROMPT_CACHE_KEY_MAX_LENGTH).join(""); } -export function applyOpenAICodexCacheAffinityHeaders(headers: Headers, sessionId: string | undefined): void { +export function applyChatGptSubscriptionCacheAffinityHeaders(headers: Headers, sessionId: string | undefined): void { if (!sessionId) return; headers.set("session-id", sessionId); headers.set("thread-id", sessionId); diff --git a/packages/ai/src/auth/AGENTS.md b/packages/ai/src/auth/AGENTS.md index c595c356ec..d7270bb06e 100644 --- a/packages/ai/src/auth/AGENTS.md +++ b/packages/ai/src/auth/AGENTS.md @@ -27,7 +27,7 @@ anthropic.ts Anthropic OAuth flow cursor.ts Cursor OAuth flow github-copilot.ts Copilot device flow kimi-coding.ts Kimi coding-plan flow -openai-codex.ts Codex flow +chatgpt-subscription.ts Codex flow openrouter.ts OpenRouter flow radius.ts Radius flow xai.ts xAI flow @@ -39,7 +39,7 @@ xai.ts xAI flow - One entry per provider id in the store; an entry may pool sibling credential slots under `accounts` while its flat top-level fields stay a valid credential (the downgrade projection older binaries read). Persistent stores are injected by the app, never assumed. - OAuth flows register through `registerBundledOAuthFlowLoaders`; don't import flow modules eagerly from browser-reachable code. - Auth resolution order in `helpers.ts` (stored credential, then env) is load-bearing; don't reorder. -- `oauth/openai-codex.ts` and `oauth/radius.ts` carry `// NEVER convert to top-level imports - breaks browser/Vite builds` on their dynamic imports. Keep both the imports and the comments. +- `oauth/chatgpt-subscription.ts` and `oauth/radius.ts` carry `// NEVER convert to top-level imports - breaks browser/Vite builds` on their dynamic imports. Keep both the imports and the comments. - `resolveProviderAuth` / `resolveProviderAuthWithSignal` in `resolve.ts` is the cross-provider choke point (credential, env, OAuth refresh, error paths); it raises `ModelsError` with a `ModelsErrorCode`, not bare `Error`. ## WHERE TO LOOK @@ -50,4 +50,4 @@ xai.ts xAI flow | Header semantics | `headers.ts` | | Credential persistence | `credential-store.ts` + app-side injected store | | Env-vs-credential precedence | `helpers.ts`, `resolve.ts` | -| Codex auth token helpers shared with `../api/` | `../utils/openai-codex-auth.ts` | +| Codex auth token helpers shared with `../api/` | `../utils/chatgpt-subscription-auth.ts` | diff --git a/packages/ai/src/auth/oauth/openai-codex.ts b/packages/ai/src/auth/oauth/chatgpt-subscription.ts similarity index 90% rename from packages/ai/src/auth/oauth/openai-codex.ts rename to packages/ai/src/auth/oauth/chatgpt-subscription.ts index 243bf2fc97..9fdc61b866 100644 --- a/packages/ai/src/auth/oauth/openai-codex.ts +++ b/packages/ai/src/auth/oauth/chatgpt-subscription.ts @@ -17,7 +17,7 @@ if (typeof process !== "undefined" && (process.versions?.node || process.version }); } -import { extractOpenAiCodexAccountId } from "../../utils/openai-codex-auth.ts"; +import { extractChatGptSubscriptionAccountId } from "../../utils/chatgpt-subscription-auth.ts"; import { getProviderEnvValue } from "../../utils/provider-env.ts"; import { getWireIdentity } from "../../wire-identity.ts"; import type { OAuthAuth, OAuthCredential, ProviderAuthInteraction } from "../types.ts"; @@ -35,8 +35,8 @@ const DEVICE_TOKEN_URL = `${AUTH_BASE_URL}/api/accounts/deviceauth/token`; const DEVICE_VERIFICATION_URI = `${AUTH_BASE_URL}/codex/device`; const DEVICE_REDIRECT_URI = `${AUTH_BASE_URL}/deviceauth/callback`; const DEVICE_CODE_TIMEOUT_SECONDS = 15 * 60; -const OPENAI_CODEX_BROWSER_LOGIN_METHOD = "browser"; -const OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD = "device_code"; +const CHATGPT_SUBSCRIPTION_BROWSER_LOGIN_METHOD = "browser"; +const CHATGPT_SUBSCRIPTION_DEVICE_CODE_LOGIN_METHOD = "device_code"; const SCOPE = "openid profile email offline_access"; type OAuthToken = { access: string; refresh: string; expires: number }; @@ -174,7 +174,7 @@ async function refreshAccessToken(refreshToken: string, signal: AbortSignal): Pr return readTokenResponse(response, "refresh"); } -async function startOpenAICodexDeviceAuth(signal: AbortSignal): Promise { +async function startChatGptSubscriptionDeviceAuth(signal: AbortSignal): Promise { const response = await fetchWithLoginCancellation(DEVICE_USER_CODE_URL, { method: "POST", headers: { "Content-Type": "application/json" }, @@ -218,7 +218,10 @@ async function startOpenAICodexDeviceAuth(signal: AbortSignal): Promise { +async function pollChatGptSubscriptionDeviceAuth( + device: DeviceAuthInfo, + signal: AbortSignal, +): Promise { return pollOAuthDeviceCodeFlow({ intervalSeconds: device.intervalSeconds, expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS, @@ -380,7 +383,7 @@ function startLocalOAuthServer(state: string): Promise { } function getAccountId(accessToken: string): string | null { - return extractOpenAiCodexAccountId(accessToken) ?? null; + return extractChatGptSubscriptionAccountId(accessToken) ?? null; } function credentialsFromToken(token: OAuthToken): OAuthCredential { @@ -407,8 +410,8 @@ async function exchangeAuthorizationCodeForCredentials( return credentialsFromToken(await exchangeAuthorizationCode(code, verifier, redirectUri, signal)); } -async function loginOpenAICodexDeviceCode(interaction: ProviderAuthInteraction): Promise { - const device = await startOpenAICodexDeviceAuth(interaction.signal); +async function loginChatGptSubscriptionDeviceCode(interaction: ProviderAuthInteraction): Promise { + const device = await startChatGptSubscriptionDeviceAuth(interaction.signal); interaction.notify({ type: "device_code", userCode: device.userCode, @@ -416,7 +419,7 @@ async function loginOpenAICodexDeviceCode(interaction: ProviderAuthInteraction): intervalSeconds: device.intervalSeconds, expiresInSeconds: DEVICE_CODE_TIMEOUT_SECONDS, }); - const code = await pollOpenAICodexDeviceAuth(device, interaction.signal); + const code = await pollChatGptSubscriptionDeviceAuth(device, interaction.signal); return exchangeAuthorizationCodeForCredentials( code.authorizationCode, code.codeVerifier, @@ -425,7 +428,7 @@ async function loginOpenAICodexDeviceCode(interaction: ProviderAuthInteraction): ); } -async function loginOpenAICodex(interaction: ProviderAuthInteraction): Promise { +async function loginChatGptSubscription(interaction: ProviderAuthInteraction): Promise { const { verifier, state, url } = await createAuthorizationFlow(); const server = await startLocalOAuthServer(state); const manualAbort = new AbortController(); @@ -491,11 +494,11 @@ async function loginOpenAICodex(interaction: ProviderAuthInteraction): Promise { +async function refreshChatGptSubscriptionToken(refreshToken: string, signal: AbortSignal): Promise { return credentialsFromToken(await refreshAccessToken(refreshToken, signal)); } -export const openaiCodexOAuth: OAuthAuth = { +export const chatgptSubscriptionOAuth: OAuthAuth = { name: "ChatGPT Subscription (Plus/Pro)", isSubscription: true, @@ -504,22 +507,22 @@ export const openaiCodexOAuth: OAuthAuth = { type: "select", message: "Select ChatGPT Subscription login method:", options: [ - { id: OPENAI_CODEX_BROWSER_LOGIN_METHOD, label: "Browser login (default)" }, - { id: OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD, label: "Device code login (headless)" }, + { id: CHATGPT_SUBSCRIPTION_BROWSER_LOGIN_METHOD, label: "Browser login (default)" }, + { id: CHATGPT_SUBSCRIPTION_DEVICE_CODE_LOGIN_METHOD, label: "Device code login (headless)" }, ], }); - if (method === OPENAI_CODEX_DEVICE_CODE_LOGIN_METHOD) { - return loginOpenAICodexDeviceCode(interaction); + if (method === CHATGPT_SUBSCRIPTION_DEVICE_CODE_LOGIN_METHOD) { + return loginChatGptSubscriptionDeviceCode(interaction); } - if (method !== OPENAI_CODEX_BROWSER_LOGIN_METHOD) { + if (method !== CHATGPT_SUBSCRIPTION_BROWSER_LOGIN_METHOD) { throw new Error(`Unknown ChatGPT Subscription login method: ${method}`); } - return loginOpenAICodex(interaction); + return loginChatGptSubscription(interaction); }, - refresh: (credential, signal) => refreshOpenAICodexToken(credential.refresh, signal), + refresh: (credential, signal) => refreshChatGptSubscriptionToken(credential.refresh, signal), async toAuth(credential) { return { apiKey: credential.access }; diff --git a/packages/ai/src/auth/oauth/load.ts b/packages/ai/src/auth/oauth/load.ts index 621296c5c4..6d4a444cbf 100644 --- a/packages/ai/src/auth/oauth/load.ts +++ b/packages/ai/src/auth/oauth/load.ts @@ -13,7 +13,7 @@ const importOAuthModule = (specifier: string): Promise => { type OAuthFlowLoaders = { anthropic: () => OAuthAuth | Promise; - openaiCodex: () => OAuthAuth | Promise; + chatgptSubscription: () => OAuthAuth | Promise; githubCopilot: () => OAuthAuth | Promise; openrouter: () => OAuthAuth | Promise; kimiCoding: () => OAuthAuth | Promise; @@ -35,9 +35,10 @@ export const loadAnthropicOAuth = async (): Promise => { return ((await importOAuthModule("./anthropic.ts")) as { anthropicOAuth: OAuthAuth }).anthropicOAuth; }; -export const loadOpenAICodexOAuth = async (): Promise => { - if (bundledLoaders) return bundledLoaders.openaiCodex(); - return ((await importOAuthModule("./openai-codex.ts")) as { openaiCodexOAuth: OAuthAuth }).openaiCodexOAuth; +export const loadChatGptSubscriptionOAuth = async (): Promise => { + if (bundledLoaders) return bundledLoaders.chatgptSubscription(); + return ((await importOAuthModule("./chatgpt-subscription.ts")) as { chatgptSubscriptionOAuth: OAuthAuth }) + .chatgptSubscriptionOAuth; }; export const loadGitHubCopilotOAuth = async (): Promise => { diff --git a/packages/ai/src/bun-oauth.ts b/packages/ai/src/bun-oauth.ts index 0a86ce0382..3fcc94c0ad 100644 --- a/packages/ai/src/bun-oauth.ts +++ b/packages/ai/src/bun-oauth.ts @@ -1,10 +1,10 @@ import { anthropicOAuth } from "./auth/oauth/anthropic.ts"; +import { chatgptSubscriptionOAuth } from "./auth/oauth/chatgpt-subscription.ts"; import { cursorOAuth } from "./auth/oauth/cursor.ts"; import { devinOAuth } from "./auth/oauth/devin.ts"; import { githubCopilotOAuth } from "./auth/oauth/github-copilot.ts"; import { kimiCodingOAuth } from "./auth/oauth/kimi-coding.ts"; import { registerBundledOAuthFlowLoaders } from "./auth/oauth/load.ts"; -import { openaiCodexOAuth } from "./auth/oauth/openai-codex.ts"; import { openRouterOAuth } from "./auth/oauth/openrouter.ts"; import { createRadiusOAuth } from "./auth/oauth/radius.ts"; import { xaiOAuth } from "./auth/oauth/xai.ts"; @@ -13,7 +13,7 @@ import { xaiOAuth } from "./auth/oauth/xai.ts"; export function registerBunOAuthFlows(): void { registerBundledOAuthFlowLoaders({ anthropic: () => anthropicOAuth, - openaiCodex: () => openaiCodexOAuth, + chatgptSubscription: () => chatgptSubscriptionOAuth, githubCopilot: () => githubCopilotOAuth, openrouter: () => openRouterOAuth, kimiCoding: () => kimiCodingOAuth, diff --git a/packages/ai/src/changes.md b/packages/ai/src/changes.md index 4cd4546aa7..b8d9920536 100644 --- a/packages/ai/src/changes.md +++ b/packages/ai/src/changes.md @@ -1,3 +1,26 @@ +## 2026-09-22 - rename the subscription-provider symbols and modules (senpi#1989) + +### What changed + +- `packages/ai/src/providers/openai-codex.ts` -> `packages/ai/src/providers/chatgpt-subscription.ts` and `packages/ai/src/providers/openai-codex.models.ts` -> `packages/ai/src/providers/chatgpt-subscription.models.ts`; `packages/ai/src/auth/oauth/openai-codex.ts` -> `packages/ai/src/auth/oauth/chatgpt-subscription.ts`; the catalog shard `packages/ai/src/providers/data/.manifest.json` regenerated for the renamed shard. +- `packages/ai/src/index.ts` and `packages/ai/src/bun-oauth.ts`: exports follow the renamed modules and symbols. +- `packages/ai/src/providers/all.ts`: imports and the provider factory name follow the rename. +- `packages/ai/src/auth/oauth/load.ts`: the lazy OAuth module id and its dynamic import follow the renamed file. +- `packages/ai/src/api/openai-codex-responses.ts`: only the debug-stats SYMBOL renamed (`OpenAICodexWebSocketDebugStats` -> `ChatGptSubscriptionWebSocketDebugStats`). The module path, the `openai-codex-responses` api id and `OpenAICodexResponsesOptions` are FROZEN and unchanged. + +### Why + +The provider ids were renamed in earlier commits; the symbols, module names and the catalog shard still spelled the old ids, so the tree read as though two different providers existed. This commit is behaviour-free: every persisted VALUE is byte-identical, including the seven frozen tokens and the `LEGACY_PROVIDER_IDS` map keys. Symbols that HOLD a frozen value were renamed while their string contents were not, and symbols that NAME the wire api were left alone entirely. + +### Why an extension could not handle it + +These are the ai package's own provider factories, OAuth module registry and export surface, all resolved before any extension loads. + +### Expected merge conflict zones + +- `packages/ai/src/providers/all.ts` and `packages/ai/src/index.ts` export lists, against any other provider addition. +- `packages/ai/src/auth/oauth/load.ts` module map, against any other OAuth provider. + ## 2026-09-22 - legacy provider id read helpers (senpi#1989) ### What changed @@ -58,13 +81,13 @@ Slot repair runs inside `packages/ai`'s pooled-credential mutations (`repairMana ### What changed -- `packages/ai/src/providers/openai-codex.ts`: provider `id` -> `chatgpt-subscription`, `name` -> `ChatGPT Subscription`, OAuth label -> `ChatGPT Subscription (Plus/Pro)`. The function still returns `Provider<"openai-codex-responses">`. -- `packages/ai/src/providers/openai-codex.models.ts`: the catalog aggregator flattens under the new provider id. +- `packages/ai/src/providers/chatgpt-subscription.ts`: provider `id` -> `chatgpt-subscription`, `name` -> `ChatGPT Subscription`, OAuth label -> `ChatGPT Subscription (Plus/Pro)`. The function still returns `Provider<"openai-codex-responses">`. +- `packages/ai/src/providers/chatgpt-subscription.models.ts`: the catalog aggregator flattens under the new provider id. - `packages/ai/src/types.ts`: `"chatgpt-subscription"` added to `KnownProvider`; `"openai-codex"` is RETAINED as a documented legacy member so configuration written by an older build still type-checks. -- `packages/ai/src/auth/oauth/openai-codex.ts`: every user-visible label and error string now reads "ChatGPT Subscription", including the login select prompt. +- `packages/ai/src/auth/oauth/chatgpt-subscription.ts`: every user-visible label and error string now reads "ChatGPT Subscription", including the login select prompt. - `packages/ai/src/api/openai-responses.ts`, `packages/ai/src/api/openai-codex-responses.ts`, `packages/ai/src/api/azure-openai-responses.ts` and `packages/ai/src/api/openai-responses-shared.ts`: the provider comparisons that gate tool-call handling match `model.provider`, so each moved to the new id. - `packages/ai/src/live-api-gates.ts`: the env-switch map is keyed by PROVIDER ID, so its key moved with the provider. -- Catalog data (`packages/ai/src/providers/data/openai-codex.json`, its manifest) carries the new provider id; `packages/ai/src/models.generated.ts` is regenerated output. +- Catalog data (`packages/ai/src/providers/data/chatgpt-subscription.json`, its manifest) carries the new provider id; `packages/ai/src/models.generated.ts` is regenerated output. ### Why @@ -372,7 +395,7 @@ The provider id is resolved inside this package before any extension loads: the - `packages/ai/src/api/openai-codex-responses.ts`: `parseWebSocket` runs the heartbeat for the life of the request and fails the stream with the liveness error (socket closed `liveness_timeout`). `WebSocketLike` gains optional `readyState` and `ping`; the Bun proxy-aware wrapper forwards both, so `isWebSocketReusable` sees the inner socket's state on Bun. Parked connections are now watched: `parkSessionWebSocket` attaches `close`/`error` listeners that evict the cache entry immediately (`parked_close`), `unparkSessionWebSocket` detaches them on reuse, and the idle TTL goes through the same `evictSessionWebSocket`. - `packages/ai/src/api/openai-responses.ts`: `parseWebSocket` runs the same heartbeat; this transport had no idle bound of its own at all. - `packages/ai/src/utils/retry.ts`: `PROVIDER_STREAM_STALL_ERROR_PATTERN` accepts the liveness wording, so the failure takes the bounded same-model stall retry like the agent-loop idle timeout does. -- Tests: `packages/ai/test/openai-codex-websocket-liveness.test.ts` (dead after two unanswered pings well inside the idle budget; pongs alone keep a silent stream alive; no ping API leaves the idle timeout in charge; a parked socket that closes is evicted even without `readyState`), `packages/ai/test/openai-codex-websocket-bun-wrapper.test.ts` (the Bun wrapper never reuses a closed or non-open parked socket), and two classifier rows in `retry.test.ts`. +- Tests: `packages/ai/test/chatgpt-subscription-websocket-liveness.test.ts` (dead after two unanswered pings well inside the idle budget; pongs alone keep a silent stream alive; no ping API leaves the idle timeout in charge; a parked socket that closes is evicted even without `readyState`), `packages/ai/test/chatgpt-subscription-websocket-bun-wrapper.test.ts` (the Bun wrapper never reuses a closed or non-open parked socket), and two classifier rows in `retry.test.ts`. ### Why @@ -1867,9 +1890,9 @@ The divergence lives in core wiring, package identity, or build plumbing that ex `supportsThinkingTokenBudget` budget field upstream generalized is retained through the shared resolver rather than the pin's inline compat table. - `packages/ai/src/api/openai-codex-responses.ts`: the fork splits WebSocket fallback/debug state into - `openai-codex-responses/fallback-state.ts` and re-exports `OpenAICodexWebSocketDebugStats` from there; - ChatGPT account identity resolves through `extractOpenAiCodexAccountId` with an `accountId ?? apiKey` - affinity fallback, cache-affinity headers come from `applyOpenAICodexCacheAffinityHeaders`, the same + `openai-codex-responses/fallback-state.ts` and re-exports `ChatGptSubscriptionWebSocketDebugStats` from there; + ChatGPT account identity resolves through `extractChatGptSubscriptionAccountId` with an `accountId ?? apiKey` + affinity fallback, cache-affinity headers come from `applyChatGptSubscriptionCacheAffinityHeaders`, the same `supportsMax`/`supportsXhigh`/`clampMaxForOpenAI` ladder applies, and `extraBody` merges under `OPENAI_RESPONSES_RESERVED_BODY_KEYS`. - `packages/ai/src/api/azure-openai-responses.ts`: accepts upstream's `tool_choice` forwarding while @@ -1995,7 +2018,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex `hasKimiTextToolCallRecovery`, the XTML recovery stream parser), context provenance, `env-api-keys`, `auth/headers`, prompt-cache TTL constants, server-fallback receipts, stop details, tool-pair repair, visible text, block symbols (`kCursorExecResolved`), wire identity - (`getWireIdentity` / `setWireIdentity`, the senpi branding seam), and `extractOpenAiCodexAccountId`. + (`getWireIdentity` / `setWireIdentity`, the senpi branding seam), and `extractChatGptSubscriptionAccountId`. ### Why @@ -2311,7 +2334,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - `packages/ai/src/api/azure-openai-responses.ts`: `max` maps through `supportsMax` (clamped to `high` otherwise), `thinkingLevelMap` wins for adapter options, and `cacheRetention: "none"` omits `prompt_cache_key`. -- `packages/ai/src/api/openai-prompt-cache.ts`: `applyOpenAICodexCacheAffinityHeaders()` applies the +- `packages/ai/src/api/openai-prompt-cache.ts`: `applyChatGptSubscriptionCacheAffinityHeaders()` applies the complete Codex affinity tuple (`session-id`, `thread-id`, `x-client-request-id`) beside the clamped `prompt_cache_key`. @@ -2365,7 +2388,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex `wire-identity.ts` module (default token `senpi`). - `packages/ai/src/api/openai-codex-responses.ts` builds the Codex `originator` and `User-Agent` from the dynamic identity instead of the previously hardcoded `senpi` strings. -- `packages/ai/src/auth/oauth/openai-codex.ts` derives the OAuth flow's default `originator` from the +- `packages/ai/src/auth/oauth/chatgpt-subscription.ts` derives the OAuth flow's default `originator` from the same identity. ### Why @@ -2383,7 +2406,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - LOW: additive root exports in `packages/ai/src/index.ts`. - MEDIUM: `packages/ai/src/api/openai-codex-responses.ts` header builders and the originator default in - `packages/ai/src/auth/oauth/openai-codex.ts`, where upstream hardcodes `pi`. + `packages/ai/src/auth/oauth/chatgpt-subscription.ts`, where upstream hardcodes `pi`. ## Request-option and content contract: metadata hooks, affinity, native blocks (2026-08-07) @@ -3226,7 +3249,7 @@ LOW in `auth/types.ts` (additive optional field on `OAuthAuth`); LOW in `models. ### Coverage -- `../test/openai-codex-fallback-recovery.test.ts` proves the immediate SSE +- `../test/chatgpt-subscription-fallback-recovery.test.ts` proves the immediate SSE cooldown boundary, post-cooldown WebSocket recovery, and immediate recovery after production cleanup. - Existing Codex stream tests retain the post-start no-fallback guard, @@ -3262,7 +3285,7 @@ LOW in `auth/types.ts` (additive optional field on `OAuthAuth`); LOW in `models. ### Coverage -- `../test/openai-codex-cache-affinity.test.ts` drives the real SSE and +- `../test/chatgpt-subscription-cache-affinity.test.ts` drives the real SSE and WebSocket request builders, pins the complete header/body mapping, and preserves the disabled-cache boundary. @@ -4323,12 +4346,12 @@ provider request builders in `packages/ai`, below any extension-visible surface. ## 2026-05-11 - Senpi-branded Codex originator and User-Agent ### What changed and why -- `providers/openai-codex-responses.ts` `buildBaseCodexHeaders()`: changed the hardcoded `originator: "pi"` and the `User-Agent: "pi (…)"` string to `"senpi"`. Upstream chose `"pi"` as the Codex CLI identity; this fork's identity is `senpi`. -- `auth/oauth/openai-codex.ts` `createAuthorizationFlow()`: changed the default `originator` parameter from `"pi"` to `"senpi"` and updated the JSDoc on `loginOpenAICodex` accordingly. Callers can still pass their own originator. +- `providers/chatgpt-subscription-responses.ts` `buildBaseCodexHeaders()`: changed the hardcoded `originator: "pi"` and the `User-Agent: "pi (…)"` string to `"senpi"`. Upstream chose `"pi"` as the Codex CLI identity; this fork's identity is `senpi`. +- `auth/oauth/chatgpt-subscription.ts` `createAuthorizationFlow()`: changed the default `originator` parameter from `"pi"` to `"senpi"` and updated the JSDoc on `loginChatGptSubscription` accordingly. Callers can still pass their own originator. ### Files modified -- `providers/openai-codex-responses.ts` -- `auth/oauth/openai-codex.ts` +- `providers/chatgpt-subscription-responses.ts` +- `auth/oauth/chatgpt-subscription.ts` ### Why the higher-level extension system couldn't handle this alone - The originator + User-Agent headers are built inside `pi-ai`'s Codex header constructor before the request leaves the library. Coding-agent extensions cannot intercept the header construction step. @@ -4389,7 +4412,7 @@ provider request builders in `packages/ai`, below any extension-visible surface. - `providers/openai-responses.ts` - `providers/openai-completions.ts` - `providers/azure-openai-responses.ts` -- `providers/openai-codex-responses.ts` +- `providers/chatgpt-subscription-responses.ts` - `providers/mistral.ts` - `providers/google.ts` - `providers/google-vertex.ts` diff --git a/packages/ai/src/index.ts b/packages/ai/src/index.ts index faf5248d7d..5d811eaf1b 100644 --- a/packages/ai/src/index.ts +++ b/packages/ai/src/index.ts @@ -36,7 +36,10 @@ export type { GoogleApiThinkingLevel, ResolvedGoogleThinkingLevel } from "./api/ export type { GoogleVertexOptions } from "./api/google-vertex.ts"; export * from "./api/lazy.ts"; export type { MistralOptions } from "./api/mistral-conversations.ts"; -export type { OpenAICodexResponsesOptions, OpenAICodexWebSocketDebugStats } from "./api/openai-codex-responses.ts"; +export type { + ChatGptSubscriptionWebSocketDebugStats, + OpenAICodexResponsesOptions, +} from "./api/openai-codex-responses.ts"; export type { OpenAICompletionsOptions } from "./api/openai-completions.ts"; export type { OpenAIResponsesOptions } from "./api/openai-responses.ts"; export { convertResponsesMessages } from "./api/openai-responses-shared.ts"; @@ -114,13 +117,13 @@ export { isCursorExecResolved, kCursorExecResolved, } from "./utils/block-symbols.ts"; +export { extractChatGptSubscriptionAccountId } from "./utils/chatgpt-subscription-auth.ts"; export * from "./utils/diagnostics.ts"; export { dropFailedAssistantTurns } from "./utils/drop-failed-assistant-turns.ts"; export * from "./utils/empty-response-errors.ts"; export { estimateContextTokens } from "./utils/estimate.ts"; export * from "./utils/event-stream.ts"; export * from "./utils/json-parse.ts"; -export { extractOpenAiCodexAccountId } from "./utils/openai-codex-auth.ts"; export * from "./utils/overflow.ts"; export { isAnthropicApiBaseUrl, diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index 3944993a27..d28e322c22 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -25,7 +25,7 @@ import { MOONSHOTAI_MODELS } from "./providers/moonshotai.models.ts"; import { MOONSHOTAI_CN_MODELS } from "./providers/moonshotai-cn.models.ts"; import { NVIDIA_MODELS } from "./providers/nvidia.models.ts"; import { OPENAI_MODELS } from "./providers/openai.models.ts"; -import { OPENAI_CODEX_MODELS } from "./providers/openai-codex.models.ts"; +import { CHATGPT_SUBSCRIPTION_MODELS } from "./providers/chatgpt-subscription.models.ts"; import { OPENCODE_MODELS } from "./providers/opencode.models.ts"; import { OPENCODE_GO_MODELS } from "./providers/opencode-go.models.ts"; import { OPENGATEWAY_MODELS } from "./providers/opengateway.models.ts"; @@ -69,7 +69,7 @@ export const MODELS: { readonly "moonshotai-cn": typeof MOONSHOTAI_CN_MODELS; readonly "nvidia": typeof NVIDIA_MODELS; readonly "openai": typeof OPENAI_MODELS; - readonly "chatgpt-subscription": typeof OPENAI_CODEX_MODELS; + readonly "chatgpt-subscription": typeof CHATGPT_SUBSCRIPTION_MODELS; readonly "opencode": typeof OPENCODE_MODELS; readonly "opencode-go": typeof OPENCODE_GO_MODELS; readonly "opengateway": typeof OPENGATEWAY_MODELS; @@ -112,7 +112,7 @@ export const MODELS: { "moonshotai-cn": MOONSHOTAI_CN_MODELS, "nvidia": NVIDIA_MODELS, "openai": OPENAI_MODELS, - "chatgpt-subscription": OPENAI_CODEX_MODELS, + "chatgpt-subscription": CHATGPT_SUBSCRIPTION_MODELS, "opencode": OPENCODE_MODELS, "opencode-go": OPENCODE_GO_MODELS, "opengateway": OPENGATEWAY_MODELS, diff --git a/packages/ai/src/providers/all.ts b/packages/ai/src/providers/all.ts index 2da895a009..00118b8a9f 100644 --- a/packages/ai/src/providers/all.ts +++ b/packages/ai/src/providers/all.ts @@ -10,6 +10,7 @@ import { azureOpenAIResponsesProvider } from "./azure-openai-responses.ts"; import { baiProvider } from "./bai.ts"; import { basetenProvider } from "./baseten.ts"; import { cerebrasProvider } from "./cerebras.ts"; +import { chatgptSubscriptionProvider } from "./chatgpt-subscription.ts"; import { cloudflareAIGatewayProvider } from "./cloudflare-ai-gateway.ts"; import { cloudflareWorkersAIProvider } from "./cloudflare-workers-ai.ts"; import { cursorProvider } from "./cursor.ts"; @@ -32,7 +33,6 @@ import { moonshotaiCnProvider } from "./moonshotai-cn.ts"; import { nvidiaProvider } from "./nvidia.ts"; import { ollamaProvider } from "./ollama.ts"; import { openaiProvider } from "./openai.ts"; -import { openaiCodexProvider } from "./openai-codex.ts"; import { openaiImagesProvider } from "./openai-images.ts"; import { opencodeProvider } from "./opencode.ts"; import { opencodeGoProvider } from "./opencode-go.ts"; @@ -178,7 +178,7 @@ export function builtinProviders(): Provider[] { moonshotaiCnProvider(), nvidiaProvider(), openaiProvider(), - openaiCodexProvider(), + chatgptSubscriptionProvider(), ollamaProvider(), opencodeProvider(), opencodeGoProvider(), diff --git a/packages/ai/src/providers/openai-codex.models.ts b/packages/ai/src/providers/chatgpt-subscription.models.ts similarity index 60% rename from packages/ai/src/providers/openai-codex.models.ts rename to packages/ai/src/providers/chatgpt-subscription.models.ts index ccbf0c4b2a..d133ed554e 100644 --- a/packages/ai/src/providers/openai-codex.models.ts +++ b/packages/ai/src/providers/chatgpt-subscription.models.ts @@ -1,8 +1,8 @@ // This file is auto-generated by scripts/generate-models.ts // Do not edit manually - run 'npm run generate-models' to update -import values from "./data/openai-codex.json" with { type: "json" }; +import values from "./data/chatgpt-subscription.json" with { type: "json" }; import { flattenModelCatalog, type ModelCatalog } from "../model-catalog.ts"; -export const OPENAI_CODEX_MODELS: ModelCatalog = +export const CHATGPT_SUBSCRIPTION_MODELS: ModelCatalog = flattenModelCatalog("chatgpt-subscription", values); diff --git a/packages/ai/src/providers/openai-codex.ts b/packages/ai/src/providers/chatgpt-subscription.ts similarity index 59% rename from packages/ai/src/providers/openai-codex.ts rename to packages/ai/src/providers/chatgpt-subscription.ts index e289b4b579..5260081ef1 100644 --- a/packages/ai/src/providers/openai-codex.ts +++ b/packages/ai/src/providers/chatgpt-subscription.ts @@ -1,10 +1,10 @@ import { openAICodexResponsesApi } from "../api/openai-codex-responses.lazy.ts"; import { lazyOAuth } from "../auth/helpers.ts"; -import { loadOpenAICodexOAuth } from "../auth/oauth/load.ts"; +import { loadChatGptSubscriptionOAuth } from "../auth/oauth/load.ts"; import { createProvider, type Provider } from "../models.ts"; -import { OPENAI_CODEX_MODELS } from "./openai-codex.models.ts"; +import { CHATGPT_SUBSCRIPTION_MODELS } from "./chatgpt-subscription.models.ts"; -export function openaiCodexProvider(): Provider<"openai-codex-responses"> { +export function chatgptSubscriptionProvider(): Provider<"openai-codex-responses"> { return createProvider({ id: "chatgpt-subscription", name: "ChatGPT Subscription", @@ -13,10 +13,10 @@ export function openaiCodexProvider(): Provider<"openai-codex-responses"> { oauth: lazyOAuth({ name: "ChatGPT Subscription (Plus/Pro)", isSubscription: true, - load: loadOpenAICodexOAuth, + load: loadChatGptSubscriptionOAuth, }), }, - models: Object.values(OPENAI_CODEX_MODELS), + models: Object.values(CHATGPT_SUBSCRIPTION_MODELS), api: openAICodexResponsesApi(), }); } diff --git a/packages/ai/src/providers/data/.manifest.json b/packages/ai/src/providers/data/.manifest.json index 8d491c30eb..70a0f40f51 100644 --- a/packages/ai/src/providers/data/.manifest.json +++ b/packages/ai/src/providers/data/.manifest.json @@ -1 +1 @@ -{"schemaVersion":3,"generatedAt":"2026-09-22T09:37:31.184Z","structureHash":"e885cac8a2f639b4f3ce9b22b3b89d6abe5238adcc21e59b889a7f70b9f1ecf7","files":{"alibaba-token-plan.json":"3a9c1be76d47459371e9fa2feeffc31e7edbbfec65fa9cc41236988efc4cf6d9","amazon-bedrock.json":"d82a76308591b1777c351a22f1bd3e4f332a50464743dfc3610b250e265e531d","ant-ling.json":"4979fe79d99ed97382d7ce40170c6132e932e77906de1eb54464c042e7d63633","anthropic.json":"e9d883ee35d94dba963320b5496bd7dd368d226cbafcf134bfec66a0a24b3b7f","azure-openai-responses.json":"9d2137c68fbf7012872296a02a13a138f7d5246dc09f2ca79e34e6a875b11488","bai.json":"2b12b15bd43462e60711944dd7283db8676611d9d76c82c2a01f64913e16a67e","baseten.json":"d155c434294b5167244cffb6dea6793f9669e91e8ab65b564f9510d81b330e37","cerebras.json":"9eaa554e1b02f3665f8f63e6432d253bf81351af7c72001054dd8108aa528a52","cloudflare-ai-gateway.json":"a2b9fa9b7bc78df3459a9c53282a9c2f715b7ce703ae0d09c59bec0a6841af49","cloudflare-workers-ai.json":"7fafe70c396bfeced683d348d743ca7ae109f03b8a6083a37a36d3698090a197","deepseek.json":"b5f9693ec641e9a957dc5f7a22042b3bb928bac3d7268406619b6f18e21c4b83","fireworks.json":"6283651d25a8861e7d42fe2bdb1cb7dd80802811dfaf61e40c11cc813408c294","github-copilot.json":"beee18c9dd4cda307140d9a54da4955a2ac1142c26d1fcbcd684181b83a7ba15","google-vertex.json":"e1631109172dc1f7d7977570420a5bb86258bdd1088427e244a2c51545c4f24d","google.json":"7619bf03edd2a7acb06a2274a14b13c3a61354288abe029a758401d4de36a8fd","groq.json":"e4728fd72e358cd8f25e9a518e9b49a521ef44523d3debe38a7db9e0663c4077","huggingface.json":"2f93082144a7b2a1cbf79148244ce7559e0428d6310c5eb0648bca89b15a8b4b","minimax-cn.json":"65d37e55a6d9585855f94b3fb7739308d107fe248ebd9e1001d1c5d4606ad2c4","minimax.json":"c7dc5765995ae77242916430929c7cc9a782589f7e483e13f7e01663190d028e","mistral.json":"be522597b22bceeb0bb9a0ba1781647649ffd19637d0c8e67b056d7b0ec33d52","moonshotai-cn.json":"c4e1801b5dfd61bebcaa33cbfc5ed0c69df14a3bee37da218a558668d817864e","moonshotai.json":"3dab637e7e2648792c786157f85f29026b93393f7098b77593c1180928d371c9","nvidia.json":"6493e09b5db8b8fe9c8fae5bc15dc287645f37f83a4ed3a1ad1181e5830b5ff7","openai-codex.json":"a2b609fe83947ef48639926b7a1838bd52c6a1400329ad14187f86220c027965","openai.json":"203a1d6bf61178268edf3d3f0ad4a6408eded401c4472bda4ac0ec73142f38ce","opencode-go.json":"64ea73012b153ef5d618663b6b542d527e43383e63b6b49f5961b310508c41cd","opencode.json":"c791718cc9f16789db88c7859b2fe94ce5e23a0aad72030422a23d7800ab2e0c","opengateway.json":"3c6a210a22965ded5f032e0a963649d809b889762205b8458cc367e5ba1e39a5","openrouter.json":"da9206877dc80b0c1678eb41cbb530a008a1d9612fc7fddc51dc3606802b08a1","qwen-token-plan-cn.json":"4bf520ed38e5c3045680d2ec2816fa8a69079dbe97f7b929767a63172288da6b","qwen-token-plan-individual.json":"eb040e78595853e980011308b1bf9caacae6dc8a91abbac5ffebd402515dd2a6","qwen-token-plan.json":"a5c67213745329ec3ba79f712f31c1c32a781d7ffb8c04f9819f36c27672531f","together.json":"927c707d06f2236cdab13f14c870fb58bce0614ba7287bbcd57cc9fd66d191ed","venice.json":"790725da45ac4b1977e21217f50ae97e2ef9b0381079aaf034935155bfab1129","vercel-ai-gateway.json":"8ad04c9c0834fbfd48bc33e4e653d33b25f76a43f601eb2be31d88c31bbdcca0","xai.json":"9b23faa5218e966a17108c05d2498a617ade1eae9b0d0684284c77d67949a3f1","xiaomi-token-plan-ams.json":"7df74a255259fc1d5be8e682c3ffb0908eaaf0af711f6433dedcf345ce3e8393","xiaomi-token-plan-cn.json":"7dc672723d5b37e5b92605752f74f46f44eba10be2b0cb959790260b9e0c2a4e","xiaomi-token-plan-sgp.json":"69b333b158be038459a511a673f4536a5b03d4931c3119339f1ac741bc03d502","xiaomi.json":"333299657f211ad9edac0f3d4298b7e5776626914c9c80e22305a87533f23966","zai-coding-cn.json":"69c572aa75104f16ccbddbaace60c267f4bcc74f86d34891ca43110d312fb1ad","zai.json":"15c8182a88e58bdc1b1c3eb0603c690475342c8a826ceddfe9af1b36b28345ee"}} +{"schemaVersion":3,"generatedAt":"2026-09-22T15:58:12.020Z","structureHash":"b5ef7cc1bcd43e157e3e91a4e0f0784b6ccc20bd73ec22d435bf2fca0d281e15","files":{"alibaba-token-plan.json":"3a9c1be76d47459371e9fa2feeffc31e7edbbfec65fa9cc41236988efc4cf6d9","amazon-bedrock.json":"d82a76308591b1777c351a22f1bd3e4f332a50464743dfc3610b250e265e531d","ant-ling.json":"4979fe79d99ed97382d7ce40170c6132e932e77906de1eb54464c042e7d63633","anthropic.json":"e9d883ee35d94dba963320b5496bd7dd368d226cbafcf134bfec66a0a24b3b7f","azure-openai-responses.json":"9d2137c68fbf7012872296a02a13a138f7d5246dc09f2ca79e34e6a875b11488","bai.json":"2b12b15bd43462e60711944dd7283db8676611d9d76c82c2a01f64913e16a67e","baseten.json":"d155c434294b5167244cffb6dea6793f9669e91e8ab65b564f9510d81b330e37","cerebras.json":"9eaa554e1b02f3665f8f63e6432d253bf81351af7c72001054dd8108aa528a52","chatgpt-subscription.json":"a2b609fe83947ef48639926b7a1838bd52c6a1400329ad14187f86220c027965","cloudflare-ai-gateway.json":"a2b9fa9b7bc78df3459a9c53282a9c2f715b7ce703ae0d09c59bec0a6841af49","cloudflare-workers-ai.json":"7fafe70c396bfeced683d348d743ca7ae109f03b8a6083a37a36d3698090a197","deepseek.json":"b5f9693ec641e9a957dc5f7a22042b3bb928bac3d7268406619b6f18e21c4b83","fireworks.json":"6283651d25a8861e7d42fe2bdb1cb7dd80802811dfaf61e40c11cc813408c294","github-copilot.json":"beee18c9dd4cda307140d9a54da4955a2ac1142c26d1fcbcd684181b83a7ba15","google-vertex.json":"e1631109172dc1f7d7977570420a5bb86258bdd1088427e244a2c51545c4f24d","google.json":"7619bf03edd2a7acb06a2274a14b13c3a61354288abe029a758401d4de36a8fd","groq.json":"e4728fd72e358cd8f25e9a518e9b49a521ef44523d3debe38a7db9e0663c4077","huggingface.json":"2f93082144a7b2a1cbf79148244ce7559e0428d6310c5eb0648bca89b15a8b4b","minimax-cn.json":"65d37e55a6d9585855f94b3fb7739308d107fe248ebd9e1001d1c5d4606ad2c4","minimax.json":"c7dc5765995ae77242916430929c7cc9a782589f7e483e13f7e01663190d028e","mistral.json":"be522597b22bceeb0bb9a0ba1781647649ffd19637d0c8e67b056d7b0ec33d52","moonshotai-cn.json":"c4e1801b5dfd61bebcaa33cbfc5ed0c69df14a3bee37da218a558668d817864e","moonshotai.json":"3dab637e7e2648792c786157f85f29026b93393f7098b77593c1180928d371c9","nvidia.json":"6493e09b5db8b8fe9c8fae5bc15dc287645f37f83a4ed3a1ad1181e5830b5ff7","openai.json":"203a1d6bf61178268edf3d3f0ad4a6408eded401c4472bda4ac0ec73142f38ce","opencode-go.json":"64ea73012b153ef5d618663b6b542d527e43383e63b6b49f5961b310508c41cd","opencode.json":"c791718cc9f16789db88c7859b2fe94ce5e23a0aad72030422a23d7800ab2e0c","opengateway.json":"3c6a210a22965ded5f032e0a963649d809b889762205b8458cc367e5ba1e39a5","openrouter.json":"da9206877dc80b0c1678eb41cbb530a008a1d9612fc7fddc51dc3606802b08a1","qwen-token-plan-cn.json":"4bf520ed38e5c3045680d2ec2816fa8a69079dbe97f7b929767a63172288da6b","qwen-token-plan-individual.json":"eb040e78595853e980011308b1bf9caacae6dc8a91abbac5ffebd402515dd2a6","qwen-token-plan.json":"a5c67213745329ec3ba79f712f31c1c32a781d7ffb8c04f9819f36c27672531f","together.json":"927c707d06f2236cdab13f14c870fb58bce0614ba7287bbcd57cc9fd66d191ed","venice.json":"790725da45ac4b1977e21217f50ae97e2ef9b0381079aaf034935155bfab1129","vercel-ai-gateway.json":"8ad04c9c0834fbfd48bc33e4e653d33b25f76a43f601eb2be31d88c31bbdcca0","xai.json":"9b23faa5218e966a17108c05d2498a617ade1eae9b0d0684284c77d67949a3f1","xiaomi-token-plan-ams.json":"7df74a255259fc1d5be8e682c3ffb0908eaaf0af711f6433dedcf345ce3e8393","xiaomi-token-plan-cn.json":"7dc672723d5b37e5b92605752f74f46f44eba10be2b0cb959790260b9e0c2a4e","xiaomi-token-plan-sgp.json":"69b333b158be038459a511a673f4536a5b03d4931c3119339f1ac741bc03d502","xiaomi.json":"333299657f211ad9edac0f3d4298b7e5776626914c9c80e22305a87533f23966","zai-coding-cn.json":"69c572aa75104f16ccbddbaace60c267f4bcc74f86d34891ca43110d312fb1ad","zai.json":"15c8182a88e58bdc1b1c3eb0603c690475342c8a826ceddfe9af1b36b28345ee"}} diff --git a/packages/ai/src/providers/data/openai-codex.json b/packages/ai/src/providers/data/chatgpt-subscription.json similarity index 100% rename from packages/ai/src/providers/data/openai-codex.json rename to packages/ai/src/providers/data/chatgpt-subscription.json diff --git a/packages/ai/src/utils/AGENTS.md b/packages/ai/src/utils/AGENTS.md index 04712bab60..e19e34057a 100644 --- a/packages/ai/src/utils/AGENTS.md +++ b/packages/ai/src/utils/AGENTS.md @@ -21,7 +21,7 @@ event-stream.ts EventStream — the async-iteration primitive behi json-parse.ts repairJson / parseJsonWithRepair / parseStreamingJson provider-env.ts getProviderEnvValue — the only sanctioned ProviderEnv read node-http-proxy.ts Proxy URL resolution from env (no Node imports; pure URL logic) -openai-codex-auth.ts Codex auth helpers shared by api/ and auth/ +chatgpt-subscription-auth.ts Codex auth helpers shared by api/ and auth/ error-body.ts, stop-details.ts, estimate.ts, diagnostics.ts, validation.ts, sanitize-unicode.ts, visible-text.ts, text.ts, block-symbols.ts, headers.ts, hash.ts, uuid.ts, abort.ts, abort-signals.ts, pi-user-agent.ts, diff --git a/packages/ai/src/utils/openai-codex-auth.ts b/packages/ai/src/utils/chatgpt-subscription-auth.ts similarity index 79% rename from packages/ai/src/utils/openai-codex-auth.ts rename to packages/ai/src/utils/chatgpt-subscription-auth.ts index 3a944856c6..113e8eb352 100644 --- a/packages/ai/src/utils/openai-codex-auth.ts +++ b/packages/ai/src/utils/chatgpt-subscription-auth.ts @@ -1,5 +1,5 @@ /** The JWT claim holding the ChatGPT account that owns a Codex credential. */ -export const OPENAI_CODEX_AUTH_CLAIM_PATH = "https://api.openai.com/auth"; +export const CHATGPT_SUBSCRIPTION_AUTH_CLAIM_PATH = "https://api.openai.com/auth"; /** * Reads the stable ChatGPT account identifier from a Codex access token. @@ -8,7 +8,7 @@ export const OPENAI_CODEX_AUTH_CLAIM_PATH = "https://api.openai.com/auth"; * code can share it without pulling Node-only authentication modules into * browser consumers. */ -export function extractOpenAiCodexAccountId(token: string): string | undefined { +export function extractChatGptSubscriptionAccountId(token: string): string | undefined { try { const parts = token.split("."); const encodedPayload = parts.length === 3 ? parts[1] : undefined; @@ -16,7 +16,7 @@ export function extractOpenAiCodexAccountId(token: string): string | undefined { const base64 = encodedPayload.replaceAll("-", "+").replaceAll("_", "/"); const payload: unknown = JSON.parse(atob(base64.padEnd(Math.ceil(base64.length / 4) * 4, "="))); if (!payload || typeof payload !== "object") return undefined; - const auth = Reflect.get(payload, OPENAI_CODEX_AUTH_CLAIM_PATH); + const auth = Reflect.get(payload, CHATGPT_SUBSCRIPTION_AUTH_CLAIM_PATH); if (!auth || typeof auth !== "object") return undefined; const accountId = Reflect.get(auth, "chatgpt_account_id"); return typeof accountId === "string" && accountId.length > 0 ? accountId : undefined; diff --git a/packages/ai/test/AGENTS.md b/packages/ai/test/AGENTS.md index 3f5a7788cd..507c6bd888 100644 --- a/packages/ai/test/AGENTS.md +++ b/packages/ai/test/AGENTS.md @@ -6,7 +6,7 @@ Flat Vitest suite for the whole `pi-ai` package: 228 top-level `.ts` files, ~53k ## LAYOUT -No mirroring of `src/` structure. Filenames encode provider + behavior: `anthropic-*`, `bedrock-*`, `openai-completions-*`, `openai-codex-*`, `azure-openai-*`, `mistral-*`, `cursor-agent*`. Cross-cutting suites are named by concern: `stream`, `models-runtime`, `retry`, `retry-hint`, `total-tokens`, `context-overflow`, `cache-retention`, `empty`, `unicode-surrogate`, `cross-provider-handoff`. +No mirroring of `src/` structure. Filenames encode provider + behavior: `anthropic-*`, `bedrock-*`, `openai-completions-*`, `chatgpt-subscription-*` (renamed from `openai-codex-*` on 2026-09-22), `azure-openai-*`, `mistral-*`, `cursor-agent*`. Cross-cutting suites are named by concern: `stream`, `models-runtime`, `retry`, `retry-hint`, `total-tokens`, `context-overflow`, `cache-retention`, `empty`, `unicode-surrogate`, `cross-provider-handoff`. ## SHARED HELPERS (non-`.test.ts` modules) @@ -53,4 +53,4 @@ PI_ENABLE_LIVE_API_TESTS=1 bun run --cwd packages/ai test # opt into live suit ## HOTSPOTS -`openai-codex-stream.test.ts` (2850), `openai-completions-tool-choice.test.ts` (1969), `stream.test.ts` (1802, multi-provider matrix + local Ollama process), `models-runtime.test.ts` (1193, provider/auth/refresh/cancel runtime), `total-tokens.test.ts` (912), `unicode-surrogate.test.ts` (866), `empty.test.ts` (861), `anthropic-provider-native-replay.test.ts` (832). Touch shared conventions here first. +`chatgpt-subscription-stream.test.ts` (2850), `openai-completions-tool-choice.test.ts` (1969), `stream.test.ts` (1802, multi-provider matrix + local Ollama process), `models-runtime.test.ts` (1193, provider/auth/refresh/cancel runtime), `total-tokens.test.ts` (912), `unicode-surrogate.test.ts` (866), `empty.test.ts` (861), `anthropic-provider-native-replay.test.ts` (832). Touch shared conventions here first. diff --git a/packages/ai/test/abort.test.ts b/packages/ai/test/abort.test.ts index 8595c7f79d..eaa0c37ea5 100644 --- a/packages/ai/test/abort.test.ts +++ b/packages/ai/test/abort.test.ts @@ -10,7 +10,7 @@ import { BASETEN_LIVE_TEST_FLAG, getLiveEnvApiKey, QWEN_TOKEN_PLAN_LIVE_TEST_FLA import { resolveApiKey } from "./oauth.ts"; // Resolve OAuth tokens at module level (async, runs before tests) -const [openaiCodexToken] = await Promise.all([resolveApiKey("chatgpt-subscription")]); +const [chatgptSubscriptionToken] = await Promise.all([resolveApiKey("chatgpt-subscription")]); const anthropicOAuthToken = getLiveEnvApiKey("ANTHROPIC_OAUTH_TOKEN", "PI_ENABLE_ANTHROPIC_OAUTH_LIVE"); const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -350,14 +350,14 @@ describe("AI Providers Abort Tests", () => { }); describe("OpenAI Codex Provider Abort", () => { - it.skipIf(!openaiCodexToken)("should abort mid-stream", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should abort mid-stream", { retry: 3 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testAbortSignal(llm, { apiKey: openaiCodexToken }); + await testAbortSignal(llm, { apiKey: chatgptSubscriptionToken }); }); - it.skipIf(!openaiCodexToken)("should handle immediate abort", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should handle immediate abort", { retry: 3 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testImmediateAbort(llm, { apiKey: openaiCodexToken }); + await testImmediateAbort(llm, { apiKey: chatgptSubscriptionToken }); }); }); diff --git a/packages/ai/test/anthropic-subscription-rename.test.ts b/packages/ai/test/anthropic-subscription-rename.test.ts index 17e100ab85..28eb9b5b2b 100644 --- a/packages/ai/test/anthropic-subscription-rename.test.ts +++ b/packages/ai/test/anthropic-subscription-rename.test.ts @@ -9,7 +9,7 @@ import { resolvePromptCacheTtlSeconds } from "../src/utils/prompt-cache-ttl.ts"; * Provider rename: `claude-sdk-oauth` -> `anthropic-subscription` (display name * "Claude SDK OAuth" -> "Anthropic Subscription"). The wire api id * `claude-sdk-oauth` is intentionally FROZEN (see - * packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/api-id.ts), + * packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/api-id.ts), * and so is every persisted token derived from the old provider id: stored * credentials still carry the managed-sentinel material `claude-sdk-oauth-managed` * verbatim, so matchers must accept BOTH the canonical and the legacy material. diff --git a/packages/ai/test/openai-codex-cache-affinity-e2e.test.ts b/packages/ai/test/chatgpt-subscription-cache-affinity-e2e.test.ts similarity index 100% rename from packages/ai/test/openai-codex-cache-affinity-e2e.test.ts rename to packages/ai/test/chatgpt-subscription-cache-affinity-e2e.test.ts diff --git a/packages/ai/test/openai-codex-cache-affinity.test.ts b/packages/ai/test/chatgpt-subscription-cache-affinity.test.ts similarity index 97% rename from packages/ai/test/openai-codex-cache-affinity.test.ts rename to packages/ai/test/chatgpt-subscription-cache-affinity.test.ts index 178861dccf..f850ed3aef 100644 --- a/packages/ai/test/openai-codex-cache-affinity.test.ts +++ b/packages/ai/test/chatgpt-subscription-cache-affinity.test.ts @@ -1,8 +1,8 @@ import { zstdDecompressSync } from "node:zlib"; import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import type { Context, Model } from "../src/types.ts"; @@ -29,8 +29,8 @@ const context: Context = { afterEach(() => { vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); }); function mockToken(): string { diff --git a/packages/ai/test/openai-codex-fallback-recovery.test.ts b/packages/ai/test/chatgpt-subscription-fallback-recovery.test.ts similarity index 92% rename from packages/ai/test/openai-codex-fallback-recovery.test.ts rename to packages/ai/test/chatgpt-subscription-fallback-recovery.test.ts index a08838e414..36e194fbee 100644 --- a/packages/ai/test/openai-codex-fallback-recovery.test.ts +++ b/packages/ai/test/chatgpt-subscription-fallback-recovery.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - getOpenAICodexWebSocketDebugStats, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + getChatGptSubscriptionWebSocketDebugStats, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import type { Context, Model } from "../src/types.ts"; @@ -30,8 +30,8 @@ const context: Context = { afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); }); function mockToken(): string { @@ -210,14 +210,14 @@ describe("OpenAI Codex WebSocket fallback recovery", () => { const transport = installTransportHarness(); await runSession("fallback-cleanup"); - expect(getOpenAICodexWebSocketDebugStats("fallback-cleanup")?.websocketFallbackActive).toBe(true); + expect(getChatGptSubscriptionWebSocketDebugStats("fallback-cleanup")?.websocketFallbackActive).toBe(true); - closeOpenAICodexWebSocketSessions("fallback-cleanup"); + closeChatGptSubscriptionWebSocketSessions("fallback-cleanup"); await runSession("fallback-cleanup"); expect(transport.connections()).toBe(2); expect(transport.fetches()).toBe(1); - const stats = getOpenAICodexWebSocketDebugStats("fallback-cleanup"); + const stats = getChatGptSubscriptionWebSocketDebugStats("fallback-cleanup"); expect(stats?.websocketFallbackActive).not.toBe(true); expect(stats?.websocketFailures).toBe(0); expect(stats?.sseFallbacks).toBe(0); diff --git a/packages/ai/test/openai-codex-oauth.test.ts b/packages/ai/test/chatgpt-subscription-oauth.test.ts similarity index 95% rename from packages/ai/test/openai-codex-oauth.test.ts rename to packages/ai/test/chatgpt-subscription-oauth.test.ts index c7aa09863c..1ccd18936d 100644 --- a/packages/ai/test/openai-codex-oauth.test.ts +++ b/packages/ai/test/chatgpt-subscription-oauth.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from "vitest"; -import { openaiCodexOAuth } from "../src/auth/oauth/openai-codex.ts"; +import { chatgptSubscriptionOAuth } from "../src/auth/oauth/chatgpt-subscription.ts"; const neverAbortedSignal = new AbortController().signal; @@ -43,7 +43,7 @@ function deviceAuthPendingResponse(): Response { ); } -function loginOpenAICodexDeviceCodeForTest(options: { +function loginChatGptSubscriptionDeviceCodeForTest(options: { onDeviceCode(info: { userCode: string; verificationUri: string; @@ -52,7 +52,7 @@ function loginOpenAICodexDeviceCodeForTest(options: { }): void; signal?: AbortSignal; }) { - return openaiCodexOAuth.login({ + return chatgptSubscriptionOAuth.login({ signal: options.signal ?? neverAbortedSignal, prompt: async (prompt) => { if (prompt.type !== "select") throw new Error(`Unexpected prompt: ${prompt.type}`); @@ -147,7 +147,7 @@ describe("OpenAI Codex OAuth", () => { vi.stubGlobal("fetch", fetchMock); - const credentialsPromise = loginOpenAICodexDeviceCodeForTest({ + const credentialsPromise = loginChatGptSubscriptionDeviceCodeForTest({ onDeviceCode: (info) => deviceInfos.push(info), }); @@ -221,7 +221,7 @@ describe("OpenAI Codex OAuth", () => { ); await expect( - openaiCodexOAuth.login({ + chatgptSubscriptionOAuth.login({ signal: neverAbortedSignal, prompt: async (prompt) => { if (prompt.type !== "select") throw new Error("Text prompt should not be used"); @@ -265,7 +265,7 @@ describe("OpenAI Codex OAuth", () => { it("cancels when OpenAI Codex login method selection is cancelled", async () => { await expect( - openaiCodexOAuth.login({ + chatgptSubscriptionOAuth.login({ signal: neverAbortedSignal, prompt: async () => { throw new Error("Login cancelled"); @@ -300,7 +300,7 @@ describe("OpenAI Codex OAuth", () => { }), ); - const credentialsPromise = loginOpenAICodexDeviceCodeForTest({ + const credentialsPromise = loginChatGptSubscriptionDeviceCodeForTest({ onDeviceCode: () => {}, signal: controller.signal, }); @@ -344,7 +344,7 @@ describe("OpenAI Codex OAuth", () => { }), ); - const credentialsPromise = loginOpenAICodexDeviceCodeForTest({ + const credentialsPromise = loginChatGptSubscriptionDeviceCodeForTest({ onDeviceCode: () => {}, }); const rejectionPromise = credentialsPromise.then( @@ -407,7 +407,7 @@ describe("OpenAI Codex OAuth", () => { }), ); - const credentialsPromise = loginOpenAICodexDeviceCodeForTest({ + const credentialsPromise = loginChatGptSubscriptionDeviceCodeForTest({ onDeviceCode: () => {}, }); @@ -445,7 +445,7 @@ describe("OpenAI Codex OAuth", () => { ); await expect( - loginOpenAICodexDeviceCodeForTest({ + loginChatGptSubscriptionDeviceCodeForTest({ onDeviceCode: () => {}, }), ).rejects.toThrow( @@ -471,7 +471,7 @@ describe("OpenAI Codex OAuth", () => { ); await expect( - openaiCodexOAuth.refresh( + chatgptSubscriptionOAuth.refresh( { type: "oauth", access: "invalid-access-token", diff --git a/packages/ai/test/chatgpt-subscription-rename.test.ts b/packages/ai/test/chatgpt-subscription-rename.test.ts index ad6cbc4b79..e1942e2f15 100644 --- a/packages/ai/test/chatgpt-subscription-rename.test.ts +++ b/packages/ai/test/chatgpt-subscription-rename.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from "vitest"; -import { openaiCodexOAuth } from "../src/auth/oauth/openai-codex.ts"; +import { chatgptSubscriptionOAuth } from "../src/auth/oauth/chatgpt-subscription.ts"; import { getModel, getProviders } from "../src/compat.ts"; import { MODELS } from "../src/models.generated.ts"; import { builtinModels } from "../src/providers/all.ts"; -import { OPENAI_CODEX_MODELS } from "../src/providers/openai-codex.models.ts"; +import { CHATGPT_SUBSCRIPTION_MODELS } from "../src/providers/chatgpt-subscription.models.ts"; import type { KnownProvider } from "../src/types.ts"; /** @@ -29,7 +29,7 @@ describe("chatgpt-subscription provider rename", () => { }); it("catalog rows carry the chatgpt-subscription provider id", () => { - const models = Object.values(OPENAI_CODEX_MODELS); + const models = Object.values(CHATGPT_SUBSCRIPTION_MODELS); expect(models.length).toBeGreaterThan(0); for (const model of models) { expect(model.provider).toBe("chatgpt-subscription"); @@ -62,14 +62,14 @@ describe("chatgpt-subscription provider rename", () => { }); it("oauth flow carries the ChatGPT Subscription label", () => { - expect(openaiCodexOAuth.name).toBe("ChatGPT Subscription (Plus/Pro)"); - expect(openaiCodexOAuth.isSubscription).toBe(true); + expect(chatgptSubscriptionOAuth.name).toBe("ChatGPT Subscription (Plus/Pro)"); + expect(chatgptSubscriptionOAuth.isSubscription).toBe(true); }); it("login select prompt says Select ChatGPT Subscription login method", async () => { let captured: unknown; await expect( - openaiCodexOAuth.login({ + chatgptSubscriptionOAuth.login({ signal: new AbortController().signal, prompt: async (prompt) => { captured = prompt; diff --git a/packages/ai/test/openai-codex-stream.test.ts b/packages/ai/test/chatgpt-subscription-stream.test.ts similarity index 99% rename from packages/ai/test/openai-codex-stream.test.ts rename to packages/ai/test/chatgpt-subscription-stream.test.ts index 62cb676c04..523f5a31b5 100644 --- a/packages/ai/test/openai-codex-stream.test.ts +++ b/packages/ai/test/chatgpt-subscription-stream.test.ts @@ -5,9 +5,9 @@ import { zstdDecompressSync } from "node:zlib"; import { Type } from "typebox"; import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - getOpenAICodexWebSocketDebugStats, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + getChatGptSubscriptionWebSocketDebugStats, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, streamSimple as streamSimpleOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; @@ -22,8 +22,8 @@ afterEach(() => { } else { process.env.PI_CODING_AGENT_DIR = originalAgentDir; } - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); vi.useRealTimers(); vi.restoreAllMocks(); }); @@ -1424,7 +1424,7 @@ describe("openai-codex streaming", () => { expect(capturedWebSocketHeaders?.session_id).toBeUndefined(); expect(capturedWebSocketHeaders?.["x-client-request-id"]).toBe("session-auto"); expect(global.fetch).not.toHaveBeenCalled(); - expect(getOpenAICodexWebSocketDebugStats("session-auto")).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats("session-auto")).toMatchObject({ cachedContextRequests: 1, fullContextRequests: 1, }); @@ -1515,7 +1515,7 @@ describe("openai-codex streaming", () => { `Bearer ${mockToken("account-b")}`, ]); expect(global.fetch).not.toHaveBeenCalled(); - expect(getOpenAICodexWebSocketDebugStats("shared-session")).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats("shared-session")).toMatchObject({ connectionsCreated: 2, connectionsReused: 1, }); @@ -1611,7 +1611,7 @@ describe("openai-codex streaming", () => { expect(closedConnections).toBe(2); expect(sentBodies).toHaveLength(2); expect(sentBodies.every((body) => body.prompt_cache_key === undefined)).toBe(true); - expect(getOpenAICodexWebSocketDebugStats("one-off-summary")).toBeUndefined(); + expect(getChatGptSubscriptionWebSocketDebugStats("one-off-summary")).toBeUndefined(); expect(global.fetch).not.toHaveBeenCalled(); }); @@ -1694,7 +1694,7 @@ describe("openai-codex streaming", () => { const result = await resultPromise; expect(result.content.find((content) => content.type === "text")?.text).toBe("Hello"); expect(fetchMock).toHaveBeenCalledTimes(1); - expect(getOpenAICodexWebSocketDebugStats("ws-connect-timeout")).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats("ws-connect-timeout")).toMatchObject({ websocketFailures: 1, sseFallbacks: 1, websocketFallbackActive: true, @@ -1859,7 +1859,7 @@ describe("openai-codex streaming", () => { const result = await resultPromise; expect(result.content.find((content) => content.type === "text")?.text).toBe("Hello"); expect(fetchMock).toHaveBeenCalledTimes(1); - expect(getOpenAICodexWebSocketDebugStats("ws-idle-before-start")).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats("ws-idle-before-start")).toMatchObject({ websocketFailures: 1, sseFallbacks: 1, websocketFallbackActive: true, @@ -2050,7 +2050,7 @@ describe("openai-codex streaming", () => { expect(connections).toBe(2); expect(sentConnectionIds).toEqual([1, 2]); - expect(getOpenAICodexWebSocketDebugStats(sessionId)).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats(sessionId)).toMatchObject({ connectionsCreated: 2, connectionsReused: 0, }); @@ -2215,7 +2215,7 @@ describe("openai-codex streaming", () => { { type: "custom_tool_call_output", call_id: "call_1", output: "real result" }, { role: "user", content: [{ type: "input_text", text: "Now finish" }] }, ]); - expect(getOpenAICodexWebSocketDebugStats("session-1")).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats("session-1")).toMatchObject({ requests: 2, connectionsCreated: 1, connectionsReused: 1, @@ -2602,7 +2602,7 @@ describe("openai-codex streaming", () => { content: [{ type: "input_text", text: "Now finish" }], }); expect(fetchMock).toHaveBeenCalledTimes(recoveryTransport === "sse" ? 1 : 0); - expect(getOpenAICodexWebSocketDebugStats(sessionId)).toMatchObject({ + expect(getChatGptSubscriptionWebSocketDebugStats(sessionId)).toMatchObject({ requests: 3, connectionsCreated: 2, connectionsReused: 1, diff --git a/packages/ai/test/openai-codex-websocket-bun-wrapper.test.ts b/packages/ai/test/chatgpt-subscription-websocket-bun-wrapper.test.ts similarity index 96% rename from packages/ai/test/openai-codex-websocket-bun-wrapper.test.ts rename to packages/ai/test/chatgpt-subscription-websocket-bun-wrapper.test.ts index dc16dea470..73ac627a50 100644 --- a/packages/ai/test/openai-codex-websocket-bun-wrapper.test.ts +++ b/packages/ai/test/chatgpt-subscription-websocket-bun-wrapper.test.ts @@ -1,7 +1,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import type { Context, Model } from "../src/types.ts"; @@ -115,8 +115,8 @@ afterAll(() => { afterEach(() => { vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); vi.useRealTimers(); vi.restoreAllMocks(); }); diff --git a/packages/ai/test/openai-codex-websocket-close-diagnostics.test.ts b/packages/ai/test/chatgpt-subscription-websocket-close-diagnostics.test.ts similarity index 96% rename from packages/ai/test/openai-codex-websocket-close-diagnostics.test.ts rename to packages/ai/test/chatgpt-subscription-websocket-close-diagnostics.test.ts index 9ea847a52c..0062959801 100644 --- a/packages/ai/test/openai-codex-websocket-close-diagnostics.test.ts +++ b/packages/ai/test/chatgpt-subscription-websocket-close-diagnostics.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import type { Context, Model } from "../src/types.ts"; @@ -107,8 +107,8 @@ async function runStream(): Promise<{ stopReason: string; errorMessage?: string afterEach(() => { vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); vi.useRealTimers(); vi.restoreAllMocks(); }); diff --git a/packages/ai/test/openai-codex-websocket-liveness.test.ts b/packages/ai/test/chatgpt-subscription-websocket-liveness.test.ts similarity index 97% rename from packages/ai/test/openai-codex-websocket-liveness.test.ts rename to packages/ai/test/chatgpt-subscription-websocket-liveness.test.ts index dbe815c48f..c8c4510715 100644 --- a/packages/ai/test/openai-codex-websocket-liveness.test.ts +++ b/packages/ai/test/chatgpt-subscription-websocket-liveness.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import { @@ -147,8 +147,8 @@ const DEAD_AFTER_MS = afterEach(() => { vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); vi.useRealTimers(); vi.restoreAllMocks(); }); diff --git a/packages/ai/test/codex-websocket-cached-probe.ts b/packages/ai/test/codex-websocket-cached-probe.ts index 4679dfb295..5b7b1cfca2 100644 --- a/packages/ai/test/codex-websocket-cached-probe.ts +++ b/packages/ai/test/codex-websocket-cached-probe.ts @@ -11,9 +11,9 @@ import { join, resolve } from "node:path"; import { Type } from "typebox"; import { ModelRuntime } from "../../coding-agent/src/core/model-runtime.ts"; import { - closeOpenAICodexWebSocketSessions, - getOpenAICodexWebSocketDebugStats, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + getChatGptSubscriptionWebSocketDebugStats, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import { getModel } from "../src/compat.ts"; @@ -180,7 +180,7 @@ async function main(): Promise { tools: [deterministicProbeTool()], }; const elapsed: number[] = []; - resetOpenAICodexWebSocketDebugStats(args.sessionId); + resetChatGptSubscriptionWebSocketDebugStats(args.sessionId); console.log(`provider openai-codex, model gpt-5.5`); console.log(`sessionId ${args.sessionId}`); @@ -192,7 +192,7 @@ async function main(): Promise { for (let turn = 1; turn <= args.turns; turn++) { context.messages.push({ role: "user", content: buildPrompt(turn), timestamp: Date.now() }); - const beforeStats = getOpenAICodexWebSocketDebugStats(args.sessionId); + const beforeStats = getChatGptSubscriptionWebSocketDebugStats(args.sessionId); const started = Date.now(); let requests = 0; let assistantCount = 0; @@ -248,7 +248,7 @@ async function main(): Promise { const elapsedMs = Date.now() - started; elapsed.push(elapsedMs); - const afterStats = getOpenAICodexWebSocketDebugStats(args.sessionId); + const afterStats = getChatGptSubscriptionWebSocketDebugStats(args.sessionId); const statLine = afterStats ? `ws requests ${afterStats.requests - (beforeStats?.requests ?? 0)} | new/reused ${afterStats.connectionsCreated - (beforeStats?.connectionsCreated ?? 0)}/${afterStats.connectionsReused - (beforeStats?.connectionsReused ?? 0)} | cached ${afterStats.cachedContextRequests - (beforeStats?.cachedContextRequests ?? 0)} | store ${afterStats.storeTrueRequests - (beforeStats?.storeTrueRequests ?? 0)} | full/delta ${afterStats.fullContextRequests - (beforeStats?.fullContextRequests ?? 0)}/${afterStats.deltaRequests - (beforeStats?.deltaRequests ?? 0)}` : "ws none"; @@ -267,7 +267,7 @@ async function main(): Promise { ); } - const stats = getOpenAICodexWebSocketDebugStats(args.sessionId); + const stats = getChatGptSubscriptionWebSocketDebugStats(args.sessionId); console.log(""); console.log( [ @@ -291,7 +291,7 @@ async function main(): Promise { `lastPreviousResponseId ${stats?.lastPreviousResponseId ?? "n/a"}`, ].join(" | "), ); - closeOpenAICodexWebSocketSessions(args.sessionId); + closeChatGptSubscriptionWebSocketSessions(args.sessionId); } main().catch((error: unknown) => { diff --git a/packages/ai/test/context-overflow.test.ts b/packages/ai/test/context-overflow.test.ts index 458aa1e662..835282d2af 100644 --- a/packages/ai/test/context-overflow.test.ts +++ b/packages/ai/test/context-overflow.test.ts @@ -30,7 +30,7 @@ import { resolveApiKey } from "./oauth.ts"; // Resolve OAuth tokens at module level (async, runs before tests) const oauthTokens = await Promise.all([resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription")]); -const [githubCopilotToken, openaiCodexToken] = oauthTokens; +const [githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const openRouterApiKey = getLiveEnvApiKey("OPENROUTER_API_KEY", OPENROUTER_LIVE_TEST_FLAG); const anthropicApiKey = getLiveEnvApiKey("ANTHROPIC_API_KEY", "PI_ENABLE_ANTHROPIC_LIVE"); const anthropicOAuthToken = getLiveEnvApiKey("ANTHROPIC_OAUTH_TOKEN", "PI_ENABLE_ANTHROPIC_OAUTH_LIVE"); @@ -241,11 +241,11 @@ describe("Context overflow error handling", () => { // ============================================================================= describe("OpenAI Codex (OAuth)", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should detect overflow via isContextOverflow", async () => { const model = getModel("chatgpt-subscription", "gpt-5.5"); - const result = await testContextOverflow(model, openaiCodexToken!); + const result = await testContextOverflow(model, chatgptSubscriptionToken!); logResult(result); expect(result.stopReason).toBe("error"); diff --git a/packages/ai/test/empty.test.ts b/packages/ai/test/empty.test.ts index d69e40d349..15427deb4c 100644 --- a/packages/ai/test/empty.test.ts +++ b/packages/ai/test/empty.test.ts @@ -16,7 +16,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); const qwenTokenPlanCnApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_CN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -822,39 +822,39 @@ describe("AI Providers Empty Message Tests", () => { }); describe("OpenAI Codex Provider Empty Messages", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle empty content array", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testEmptyMessage(llm, { apiKey: openaiCodexToken }); + await testEmptyMessage(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle empty string content", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testEmptyStringMessage(llm, { apiKey: openaiCodexToken }); + await testEmptyStringMessage(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle whitespace-only content", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testWhitespaceOnlyMessage(llm, { apiKey: openaiCodexToken }); + await testWhitespaceOnlyMessage(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle empty assistant message in conversation", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testEmptyAssistantMessage(llm, { apiKey: openaiCodexToken }); + await testEmptyAssistantMessage(llm, { apiKey: chatgptSubscriptionToken }); }, ); }); diff --git a/packages/ai/test/gpt-6-astra-context-window.test.ts b/packages/ai/test/gpt-6-astra-context-window.test.ts index 8dbf494da5..86f7b60d94 100644 --- a/packages/ai/test/gpt-6-astra-context-window.test.ts +++ b/packages/ai/test/gpt-6-astra-context-window.test.ts @@ -56,9 +56,9 @@ describe("GPT-6 Astra series catalog context window", () => { "amazon-bedrock.json", "azure-openai-responses.json", "bai.json", + "chatgpt-subscription.json", "cloudflare-ai-gateway.json", "github-copilot.json", - "openai-codex.json", "openai.json", "opencode.json", "opengateway.json", diff --git a/packages/ai/test/image-tool-result.test.ts b/packages/ai/test/image-tool-result.test.ts index 06e69db927..24519bd833 100644 --- a/packages/ai/test/image-tool-result.test.ts +++ b/packages/ai/test/image-tool-result.test.ts @@ -24,7 +24,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const openRouterApiKey = getLiveEnvApiKey("OPENROUTER_API_KEY", OPENROUTER_LIVE_TEST_FLAG); const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -563,21 +563,21 @@ describe("Tool Results with Images", () => { }); describe("OpenAI Codex Provider", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle tool result with only image", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await handleToolWithImageResult(llm, { apiKey: openaiCodexToken }); + await handleToolWithImageResult(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle tool result with text and image", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await handleToolWithTextAndImageResult(llm, { apiKey: openaiCodexToken }); + await handleToolWithTextAndImageResult(llm, { apiKey: chatgptSubscriptionToken }); }, ); }); diff --git a/packages/ai/test/oauth-auth.test.ts b/packages/ai/test/oauth-auth.test.ts index ac1aa082de..793a93ec61 100644 --- a/packages/ai/test/oauth-auth.test.ts +++ b/packages/ai/test/oauth-auth.test.ts @@ -1,10 +1,10 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { InMemoryCredentialStore } from "../src/auth/credential-store.ts"; import { anthropicOAuth } from "../src/auth/oauth/anthropic.ts"; +import { chatgptSubscriptionOAuth } from "../src/auth/oauth/chatgpt-subscription.ts"; import { cursorOAuth } from "../src/auth/oauth/cursor.ts"; import { githubCopilotOAuth } from "../src/auth/oauth/github-copilot.ts"; import { kimiCodingOAuth } from "../src/auth/oauth/kimi-coding.ts"; -import { openaiCodexOAuth } from "../src/auth/oauth/openai-codex.ts"; import { openRouterOAuth } from "../src/auth/oauth/openrouter.ts"; import { xaiOAuth } from "../src/auth/oauth/xai.ts"; import { createModels } from "../src/models.ts"; @@ -31,7 +31,7 @@ describe("OAuthAuth adapters", () => { it("identifies only subscription-backed OAuth flows as subscriptions", () => { for (const oauth of [ anthropicOAuth, - openaiCodexOAuth, + chatgptSubscriptionOAuth, githubCopilotOAuth, kimiCodingOAuth, xaiOAuth, @@ -48,7 +48,7 @@ describe("OAuthAuth adapters", () => { }); it("openai-codex toAuth derives the api key from the access token", async () => { - const auth = await openaiCodexOAuth.toAuth({ type: "oauth", access: "token", refresh: "r", expires: 0 }); + const auth = await chatgptSubscriptionOAuth.toAuth({ type: "oauth", access: "token", refresh: "r", expires: 0 }); expect(auth).toEqual({ apiKey: "token" }); }); diff --git a/packages/ai/test/openai-fast-models.test.ts b/packages/ai/test/openai-fast-models.test.ts index c86c5ac5a5..2abe5f6194 100644 --- a/packages/ai/test/openai-fast-models.test.ts +++ b/packages/ai/test/openai-fast-models.test.ts @@ -29,7 +29,7 @@ const PRIORITY_TIER_MODEL_IDS = [ "o4-mini", ] as const; -const OPENAI_CODEX_PRIORITY_TIER_MODEL_IDS = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const; +const CHATGPT_SUBSCRIPTION_PRIORITY_TIER_MODEL_IDS = ["gpt-5.6-sol", "gpt-5.6-terra", "gpt-5.6-luna"] as const; const GPT_56_SOL_MODEL_IDS = ["gpt-5.6-sol", "gpt-5.6-sol-fast"] as const; const GPT_6_ASTRA_MODEL_IDS = ["gpt-6-astra", "gpt-6-astra-fast"] as const; const GPT_6_ASTRA_THINKING_LEVEL_MAP = { @@ -130,14 +130,14 @@ describe("OpenAI -fast priority-tier catalog variants", () => { it("ships a -fast variant for openai-codex priority-eligible models", () => { const codexCatalogIds = getModels("chatgpt-subscription").map((model) => model.id); - for (const id of OPENAI_CODEX_PRIORITY_TIER_MODEL_IDS) { + for (const id of CHATGPT_SUBSCRIPTION_PRIORITY_TIER_MODEL_IDS) { expect(codexCatalogIds, `codex base model ${id} should exist`).toContain(id); expect(codexCatalogIds, `codex ${id}-fast should exist`).toContain(`${id}-fast`); } }); it("clones the codex base model with upstreamModelId, priority tier, and base cost rates", () => { - for (const id of OPENAI_CODEX_PRIORITY_TIER_MODEL_IDS) { + for (const id of CHATGPT_SUBSCRIPTION_PRIORITY_TIER_MODEL_IDS) { const base = getModel("chatgpt-subscription", id); const fast = getModel("chatgpt-subscription", `${id}-fast`); expect(base, `${id} should exist`).toBeDefined(); diff --git a/packages/ai/test/openai-responses-tool-result-images.test.ts b/packages/ai/test/openai-responses-tool-result-images.test.ts index 9513a2df99..4cdf3f2130 100644 --- a/packages/ai/test/openai-responses-tool-result-images.test.ts +++ b/packages/ai/test/openai-responses-tool-result-images.test.ts @@ -15,7 +15,7 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const oauthTokens = await Promise.all([resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription")]); -const [githubCopilotToken, openaiCodexToken] = oauthTokens; +const [githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const getImageSchema = Type.Object({}); const getImageTool: Tool = { @@ -180,12 +180,12 @@ describe("Responses API tool result images", () => { describe("OpenAI Codex Responses Provider (gpt-5.5)", () => { const model = getModel("chatgpt-subscription", "gpt-5.5"); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "should send tool result images in function_call_output", { retry: 3, timeout: 30000 }, async () => { await verifyToolResultImagesStayInFunctionCallOutput(model, { - apiKey: openaiCodexToken, + apiKey: chatgptSubscriptionToken, reasoningEffort: "low", }); }, diff --git a/packages/ai/test/responseid.test.ts b/packages/ai/test/responseid.test.ts index 8842ed9132..d61aa6fa73 100644 --- a/packages/ai/test/responseid.test.ts +++ b/packages/ai/test/responseid.test.ts @@ -7,7 +7,7 @@ import { resolveApiKey } from "./oauth.ts"; type StreamOptionsWithExtras = StreamOptions & Record; const oauthTokens = await Promise.all([resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription")]); -const [githubCopilotToken, openaiCodexToken] = oauthTokens; +const [githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; async function expectResponseId(model: Model, options: StreamOptionsWithExtras = {}) { const context: Context = { @@ -112,9 +112,9 @@ describe("responseId E2E Tests", () => { }); describe("OpenAI Codex Provider", () => { - it.skipIf(!openaiCodexToken)("should expose responseId", { retry: 3, timeout: 30000 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should expose responseId", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await expectResponseId(llm, { apiKey: openaiCodexToken }); + await expectResponseId(llm, { apiKey: chatgptSubscriptionToken }); }); }); }); diff --git a/packages/ai/test/responses-completion-grace.test.ts b/packages/ai/test/responses-completion-grace.test.ts index e56343fc29..b9a637d683 100644 --- a/packages/ai/test/responses-completion-grace.test.ts +++ b/packages/ai/test/responses-completion-grace.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, it, vi } from "vitest"; import { - closeOpenAICodexWebSocketSessions, - resetOpenAICodexWebSocketDebugStats, + closeChatGptSubscriptionWebSocketSessions, + resetChatGptSubscriptionWebSocketDebugStats, stream as streamOpenAICodexResponses, } from "../src/api/openai-codex-responses.ts"; import { @@ -47,8 +47,8 @@ const completed = { type: "response.completed" }; afterEach(() => { vi.unstubAllGlobals(); - closeOpenAICodexWebSocketSessions(); - resetOpenAICodexWebSocketDebugStats(); + closeChatGptSubscriptionWebSocketSessions(); + resetChatGptSubscriptionWebSocketDebugStats(); vi.useRealTimers(); vi.restoreAllMocks(); }); diff --git a/packages/ai/test/stream.test.ts b/packages/ai/test/stream.test.ts index c5d0b3f04f..b6d558d49e 100644 --- a/packages/ai/test/stream.test.ts +++ b/packages/ai/test/stream.test.ts @@ -31,7 +31,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const openRouterApiKey = getLiveEnvApiKey("OPENROUTER_API_KEY", OPENROUTER_LIVE_TEST_FLAG); const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -1495,56 +1495,72 @@ describe("Generate E2E Tests", () => { describe("OpenAI Codex Provider (gpt-5.5)", () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - it.skipIf(!openaiCodexToken)("should complete basic text generation", { retry: 3 }, async () => { - await basicTextGeneration(llm, { apiKey: openaiCodexToken }); + it.skipIf(!chatgptSubscriptionToken)("should complete basic text generation", { retry: 3 }, async () => { + await basicTextGeneration(llm, { apiKey: chatgptSubscriptionToken }); }); - it.skipIf(!openaiCodexToken)("should handle tool calling", { retry: 3 }, async () => { - await handleToolCall(llm, { apiKey: openaiCodexToken }); + it.skipIf(!chatgptSubscriptionToken)("should handle tool calling", { retry: 3 }, async () => { + await handleToolCall(llm, { apiKey: chatgptSubscriptionToken }); }); - it.skipIf(!openaiCodexToken)("should handle streaming", { retry: 3 }, async () => { - await handleStreaming(llm, { apiKey: openaiCodexToken }); + it.skipIf(!chatgptSubscriptionToken)("should handle streaming", { retry: 3 }, async () => { + await handleStreaming(llm, { apiKey: chatgptSubscriptionToken }); }); - it.skipIf(!openaiCodexToken)("should handle thinking with reasoningEffort xhigh", { retry: 3 }, async () => { - await handleThinking(llm, { apiKey: openaiCodexToken, reasoningEffort: "xhigh" }); - }); + it.skipIf(!chatgptSubscriptionToken)( + "should handle thinking with reasoningEffort xhigh", + { retry: 3 }, + async () => { + await handleThinking(llm, { apiKey: chatgptSubscriptionToken, reasoningEffort: "xhigh" }); + }, + ); - it.skipIf(!openaiCodexToken)("should handle multi-turn with thinking and tools", { retry: 3 }, async () => { - await multiTurn(llm, { apiKey: openaiCodexToken, reasoningEffort: "xhigh" }); - }); + it.skipIf(!chatgptSubscriptionToken)( + "should handle multi-turn with thinking and tools", + { retry: 3 }, + async () => { + await multiTurn(llm, { apiKey: chatgptSubscriptionToken, reasoningEffort: "xhigh" }); + }, + ); - it.skipIf(!openaiCodexToken)("should handle image input", { retry: 3 }, async () => { - await handleImage(llm, { apiKey: openaiCodexToken }); + it.skipIf(!chatgptSubscriptionToken)("should handle image input", { retry: 3 }, async () => { + await handleImage(llm, { apiKey: chatgptSubscriptionToken }); }); }); describe("OpenAI Codex Provider (gpt-5.5 via WebSocket)", () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - const wsOptions = { apiKey: openaiCodexToken, transport: "websocket" as const }; + const wsOptions = { apiKey: chatgptSubscriptionToken, transport: "websocket" as const }; - it.skipIf(!openaiCodexToken)("should complete basic text generation", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should complete basic text generation", { retry: 3 }, async () => { await basicTextGeneration(llm, wsOptions); }); - it.skipIf(!openaiCodexToken)("should handle tool calling", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should handle tool calling", { retry: 3 }, async () => { await handleToolCall(llm, wsOptions); }); - it.skipIf(!openaiCodexToken)("should handle streaming", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should handle streaming", { retry: 3 }, async () => { await handleStreaming(llm, wsOptions); }); - it.skipIf(!openaiCodexToken)("should handle thinking with reasoningEffort xhigh", { retry: 3 }, async () => { - await handleThinking(llm, { ...wsOptions, reasoningEffort: "xhigh" }); - }); + it.skipIf(!chatgptSubscriptionToken)( + "should handle thinking with reasoningEffort xhigh", + { retry: 3 }, + async () => { + await handleThinking(llm, { ...wsOptions, reasoningEffort: "xhigh" }); + }, + ); - it.skipIf(!openaiCodexToken)("should handle multi-turn with thinking and tools", { retry: 3 }, async () => { - await multiTurn(llm, { ...wsOptions, reasoningEffort: "xhigh" }); - }); + it.skipIf(!chatgptSubscriptionToken)( + "should handle multi-turn with thinking and tools", + { retry: 3 }, + async () => { + await multiTurn(llm, { ...wsOptions, reasoningEffort: "xhigh" }); + }, + ); - it.skipIf(!openaiCodexToken)("should handle image input", { retry: 3 }, async () => { + it.skipIf(!chatgptSubscriptionToken)("should handle image input", { retry: 3 }, async () => { await handleImage(llm, wsOptions); }); }); diff --git a/packages/ai/test/tokens.test.ts b/packages/ai/test/tokens.test.ts index 38fd995e92..659929c90b 100644 --- a/packages/ai/test/tokens.test.ts +++ b/packages/ai/test/tokens.test.ts @@ -16,7 +16,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); const qwenTokenPlanCnApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_CN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -364,12 +364,12 @@ describe("Token Statistics on Abort", () => { }); describe("OpenAI Codex Provider", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should include token stats when aborted mid-stream", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testTokensOnAbort(llm, { apiKey: openaiCodexToken }); + await testTokensOnAbort(llm, { apiKey: chatgptSubscriptionToken }); }, ); }); diff --git a/packages/ai/test/tool-call-without-result.test.ts b/packages/ai/test/tool-call-without-result.test.ts index 5cbe7ed95f..b3b7113162 100644 --- a/packages/ai/test/tool-call-without-result.test.ts +++ b/packages/ai/test/tool-call-without-result.test.ts @@ -17,7 +17,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); const qwenTokenPlanCnApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_CN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -366,12 +366,12 @@ describe("Tool Call Without Result Tests", () => { }); describe("OpenAI Codex Provider", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should filter out tool calls without corresponding tool results", { retry: 3, timeout: 30000 }, async () => { const model = getModel("chatgpt-subscription", "gpt-5.5"); - await testToolCallWithoutResult(model, { apiKey: openaiCodexToken }); + await testToolCallWithoutResult(model, { apiKey: chatgptSubscriptionToken }); }, ); }); diff --git a/packages/ai/test/total-tokens.test.ts b/packages/ai/test/total-tokens.test.ts index 858c5d9100..0f32f923c8 100644 --- a/packages/ai/test/total-tokens.test.ts +++ b/packages/ai/test/total-tokens.test.ts @@ -35,7 +35,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const openRouterApiKey = getLiveEnvApiKey("OPENROUTER_API_KEY", OPENROUTER_LIVE_TEST_FLAG); const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -892,14 +892,14 @@ describe("totalTokens field", () => { // ========================================================================= describe("OpenAI Codex (OAuth)", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should return totalTokens equal to sum of components", { retry: 3, timeout: 60000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); console.log(`\nOpenAI Codex / ${llm.id}:`); - const { first, second } = await testTotalTokensWithCache(llm, { apiKey: openaiCodexToken }); + const { first, second } = await testTotalTokensWithCache(llm, { apiKey: chatgptSubscriptionToken }); logUsage("First request", first); logUsage("Second request", second); diff --git a/packages/ai/test/unicode-surrogate.test.ts b/packages/ai/test/unicode-surrogate.test.ts index 4f850c2b59..5ba363929e 100644 --- a/packages/ai/test/unicode-surrogate.test.ts +++ b/packages/ai/test/unicode-surrogate.test.ts @@ -20,7 +20,7 @@ const oauthTokens = await Promise.all([ resolveApiKey("github-copilot"), resolveApiKey("chatgpt-subscription"), ]); -const [anthropicOAuthToken, githubCopilotToken, openaiCodexToken] = oauthTokens; +const [anthropicOAuthToken, githubCopilotToken, chatgptSubscriptionToken] = oauthTokens; const basetenApiKey = getLiveEnvApiKey("BASETEN_API_KEY", BASETEN_LIVE_TEST_FLAG); const qwenTokenPlanApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); const qwenTokenPlanCnApiKey = getLiveEnvApiKey("QWEN_TOKEN_PLAN_CN_API_KEY", QWEN_TOKEN_PLAN_LIVE_TEST_FLAG); @@ -836,30 +836,30 @@ describe("AI Providers Unicode Surrogate Pair Tests", () => { }); describe("OpenAI Codex Provider Unicode Handling", () => { - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle emoji in tool results", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testEmojiInToolResults(llm, { apiKey: openaiCodexToken }); + await testEmojiInToolResults(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle real-world LinkedIn comment data with emoji", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testRealWorldLinkedInData(llm, { apiKey: openaiCodexToken }); + await testRealWorldLinkedInData(llm, { apiKey: chatgptSubscriptionToken }); }, ); - it.skipIf(!openaiCodexToken)( + it.skipIf(!chatgptSubscriptionToken)( "gpt-5.5 - should handle unpaired high surrogate (0xD83D) in tool results", { retry: 3, timeout: 30000 }, async () => { const llm = getModel("chatgpt-subscription", "gpt-5.5"); - await testUnpairedHighSurrogate(llm, { apiKey: openaiCodexToken }); + await testUnpairedHighSurrogate(llm, { apiKey: chatgptSubscriptionToken }); }, ); }); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index c26df84f37..c901263c8b 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -15,6 +15,8 @@ ### Changed +- Internal code names now follow the subscription provider rename: TypeScript symbols, module files and the builtin extension directory read `anthropic-subscription` instead of `claude-sdk-oauth`, and `chatgpt-subscription` instead of `openai-codex` in `@earendil-works/pi-ai`. Nothing you can observe changes — provider ids, the wire api id, stored files, diagnostics ids and env-var names stay byte-identical. ([#1989](https://github.com/code-yeongyu/senpi/issues/1989)) + - Upgrading across the subscription provider rename is now covered end to end: an agent directory written by an older senpi keeps its logins, settings, custom models and saved accounts, and is migrated exactly once. ([#1989](https://github.com/code-yeongyu/senpi/issues/1989)) - Settings, credentials, sessions and `models.json` written before the subscription provider rename keep working: the old provider ids are resolved on read everywhere they are stored, and senpi tells you once which ids to update in `models.json`. ([#1989](https://github.com/code-yeongyu/senpi/issues/1989)) diff --git a/packages/coding-agent/changes.md b/packages/coding-agent/changes.md index b70d0fb5b2..10d061bd7d 100644 --- a/packages/coding-agent/changes.md +++ b/packages/coding-agent/changes.md @@ -542,7 +542,7 @@ The divergence lives in core wiring, package identity, or build plumbing that ex - `packages/coding-agent/install-lock/package.json` and `packages/coding-agent/install-lock/package-lock.json`: regenerated from the refreshed root lock. - `packages/coding-agent/publish-deps.lock.json`: regenerated shrinkwrap for the same tree. - `packages/coding-agent/test/mermaid.test.ts`: the two tests covering the partial-render warning path now use input that still warns under grok-mermaid 0.2.3, which learned to render the `:::className` node syntax the old fixtures relied on failing. -- `packages/coding-agent/test/suite/claude-sdk-oauth-naming.test.ts`: asserts the upstream package name without pinning its version, since the naming boundary is the subject of the test. +- `packages/coding-agent/test/suite/anthropic-subscription-naming.test.ts`: asserts the upstream package name without pinning its version, since the naming boundary is the subject of the test. ### Why @@ -915,7 +915,7 @@ amplification or dropping classic per-event backpressure. ## 2026-07-29 — OpenAI Codex usage extension example -- Changed: added a standalone `examples/extensions/openai-codex-usage/` example that resolves Senpi-managed Codex OAuth, fetches the remaining five-hour and weekly limits, and publishes them through `ctx.ui.setStatus()`. Missing windows render as unavailable; sanitized HTTP/network/parse failures replace stale values with an unavailable status. The poller is single-flight, abortable, and cleared on model changes, shutdown, or `/usage`. +- Changed: added a standalone `examples/extensions/chatgpt-subscription-usage/` example that resolves Senpi-managed Codex OAuth, fetches the remaining five-hour and weekly limits, and publishes them through `ctx.ui.setStatus()`. Missing windows render as unavailable; sanitized HTTP/network/parse failures replace stale values with an unavailable status. The poller is single-flight, abortable, and cleared on model changes, shutdown, or `/usage`. - Why: users can see provider limits with the built-in footer or any custom footer that consumes extension statuses, without coupling usage retrieval to one footer implementation or presenting unknown/stale percentages as current. - Extension boundary: the example uses public model-registry, lifecycle, command, and status APIs; no core footer or authentication source changes are required. Deterministic fake-API and fake-timer tests cover toggle, model-change, abort, scheduled polling, and shutdown cleanup. - Merge-conflict risk: low. The change adds an isolated example directory, one test, one catalog row, documentation, and this record. diff --git a/packages/coding-agent/docs/release-guide.md b/packages/coding-agent/docs/release-guide.md index afea615dd6..071d3ed5c1 100644 --- a/packages/coding-agent/docs/release-guide.md +++ b/packages/coding-agent/docs/release-guide.md @@ -257,7 +257,7 @@ bunx --cwd packages/coding-agent vitest \ test/suite/vendored-builtins.test.ts \ test/extensions/loader-concurrency.test.ts \ test/mcp/ \ - test/suite/claude-sdk-oauth-extension.test.ts \ + test/suite/anthropic-subscription-extension.test.ts \ test/suite/terminal-extension.test.ts \ test/compaction/ \ test/ttsr/ diff --git a/packages/coding-agent/examples/extensions/AGENTS.md b/packages/coding-agent/examples/extensions/AGENTS.md index 0f3aad8e22..f8323a76d0 100644 --- a/packages/coding-agent/examples/extensions/AGENTS.md +++ b/packages/coding-agent/examples/extensions/AGENTS.md @@ -12,7 +12,7 @@ custom-provider-gitlab-duo/ Provider against GitLab Duo gondolin/ Route built-in tools + `!` commands into a Gondolin micro-VM sandbox/ OS-level sandboxing via @anthropic-ai/sandbox-runtime plan-mode/ Read-only planning mode (generated instructions) -openai-codex-usage/ Codex usage tracking +chatgpt-subscription-usage/ Codex usage tracking dynamic-resources/ Dynamic resource examples with-deps/ Extension with its own private dependency manifest doom-overlay/ WASM Doom rendered as live overlay; doom/build/ is generated diff --git a/packages/coding-agent/examples/extensions/README.md b/packages/coding-agent/examples/extensions/README.md index a763cbbf88..f6ecd6404e 100644 --- a/packages/coding-agent/examples/extensions/README.md +++ b/packages/coding-agent/examples/extensions/README.md @@ -53,7 +53,7 @@ cp permission-gate.ts ~/.senpi/agent/extensions/ | `handoff.ts` | Transfer context to a new focused session via `/handoff ` | | `qna.ts` | Extracts questions from last response into editor via `ctx.ui.setEditorText()` | | `status-line.ts` | Shows turn progress in footer via `ctx.ui.setStatus()` with themed colors | -| `openai-codex-usage/` | Shows OpenAI Codex rate-limit usage through `ctx.ui.setStatus()` | +| `chatgpt-subscription-usage/` | Shows OpenAI Codex rate-limit usage through `ctx.ui.setStatus()` | | `github-issue-autocomplete.ts` | Adds `#1234` issue completions by stacking a custom autocomplete provider that preloads open issues from `gh issue list` | | `widget-placement.ts` | Shows widgets above and below the editor via `ctx.ui.setWidget()` placement | | `hidden-thinking-label.ts` | Customizes the collapsed thinking label via `ctx.ui.setHiddenThinkingLabel()` | diff --git a/packages/coding-agent/examples/extensions/openai-codex-usage/README.md b/packages/coding-agent/examples/extensions/chatgpt-subscription-usage/README.md similarity index 100% rename from packages/coding-agent/examples/extensions/openai-codex-usage/README.md rename to packages/coding-agent/examples/extensions/chatgpt-subscription-usage/README.md diff --git a/packages/coding-agent/examples/extensions/openai-codex-usage/codex-usage.ts b/packages/coding-agent/examples/extensions/chatgpt-subscription-usage/codex-usage.ts similarity index 100% rename from packages/coding-agent/examples/extensions/openai-codex-usage/codex-usage.ts rename to packages/coding-agent/examples/extensions/chatgpt-subscription-usage/codex-usage.ts diff --git a/packages/coding-agent/examples/extensions/openai-codex-usage/index.ts b/packages/coding-agent/examples/extensions/chatgpt-subscription-usage/index.ts similarity index 93% rename from packages/coding-agent/examples/extensions/openai-codex-usage/index.ts rename to packages/coding-agent/examples/extensions/chatgpt-subscription-usage/index.ts index ad71ed2795..38429db9bf 100644 --- a/packages/coding-agent/examples/extensions/openai-codex-usage/index.ts +++ b/packages/coding-agent/examples/extensions/chatgpt-subscription-usage/index.ts @@ -1,5 +1,5 @@ import type { ExtensionAPI, ExtensionContext } from "@code-yeongyu/senpi"; -import { extractOpenAiCodexAccountId } from "@earendil-works/pi-ai"; +import { extractChatGptSubscriptionAccountId } from "@earendil-works/pi-ai"; import { type CodexUsagePollingController, @@ -49,7 +49,7 @@ export default function (pi: ExtensionAPI) { } const auth = await abortable(ctx.modelRegistry.getApiKeyAndHeaders(model), signal); if (!auth.ok || !auth.apiKey) return null; - const accountId = extractOpenAiCodexAccountId(auth.apiKey); + const accountId = extractChatGptSubscriptionAccountId(auth.apiKey); if (!accountId) return null; return fetchCodexUsage({ credentials: { @@ -84,7 +84,7 @@ export default function (pi: ExtensionAPI) { }, onError: () => { ctx.ui.setStatus(STATUS_KEY, ctx.ui.theme.fg("dim", UNAVAILABLE_STATUS)); - console.error("[openai-codex-usage] Refresh failed"); + console.error("[chatgpt-subscription-usage] Refresh failed"); }, }); }; diff --git a/packages/coding-agent/src/changes.md b/packages/coding-agent/src/changes.md index a80929a9f6..270d5331e7 100644 --- a/packages/coding-agent/src/changes.md +++ b/packages/coding-agent/src/changes.md @@ -2616,8 +2616,8 @@ other provider-bound image transport behavior that owns the same payload path. - Real-CLI QA (senpi-qa Channel 2, TUI in a pty, isolated sandbox, `PI_OFFLINE=1`): with Fable 5 served only by `claude-sdk-oauth` and no `retry.fallbackChains` configured, `/fallback` renders - `claude-sdk-oauth/claude-fable-5 -> kimi-coding/k3:max, claude-sdk-oauth/claude-opus-5:xhigh, - claude-sdk-oauth/claude-opus-4-8:xhigh` where the previous default produced no chain at all. That QA run is + `anthropic-subscription/claude-fable-5 -> kimi-coding/k3:max, anthropic-subscription/claude-opus-5:xhigh, + anthropic-subscription/claude-opus-4-8:xhigh` where the previous default produced no chain at all. That QA run is what surfaced both the fan-out cap and the display scoping above; neither was visible to unit fixtures. ### Expected merge conflict zones on next upstream sync diff --git a/packages/coding-agent/src/core/agent-session.ts b/packages/coding-agent/src/core/agent-session.ts index 82a92b6dba..40d4ae2630 100644 --- a/packages/coding-agent/src/core/agent-session.ts +++ b/packages/coding-agent/src/core/agent-session.ts @@ -129,7 +129,7 @@ import { import { areExperimentalFeaturesEnabled } from "./experimental.ts"; import { exportSessionToHtml, type ToolHtmlRenderer } from "./export-html/index.ts"; import { createToolHtmlRenderer } from "./export-html/tool-renderer.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./extensions/builtin/claude-sdk-oauth/account-management.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./extensions/builtin/anthropic-subscription/account-management.ts"; import { type ModelUsabilityAdmission, ModelUsabilityBudgetError, @@ -8144,7 +8144,7 @@ export class AgentSession { * escalate to the fallback chain when that budget runs out. */ private _isClaudeSdkSameModelRemintError(message: AssistantMessage): boolean { - if (this.model?.provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) return false; + if (this.model?.provider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) return false; return this._isClaudeSdkSessionLockError(message) || this._isClaudeSdkInvalidRequestError(message); } @@ -8156,8 +8156,8 @@ export class AgentSession { */ private _isClaudeSdkAuthMissError(message: AssistantMessage): boolean { return ( - this.model?.provider === CLAUDE_SDK_OAUTH_PROVIDER_ID && - message.errorMessage === providerNotConfiguredMessage(CLAUDE_SDK_OAUTH_PROVIDER_ID) + this.model?.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID && + message.errorMessage === providerNotConfiguredMessage(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) ); } diff --git a/packages/coding-agent/src/core/changes.md b/packages/coding-agent/src/core/changes.md index e843516306..419a3588ba 100644 --- a/packages/coding-agent/src/core/changes.md +++ b/packages/coding-agent/src/core/changes.md @@ -44,7 +44,7 @@ CLI parsing and the interactive login command are core surfaces that run before ### What changed -- `packages/coding-agent/src/core/settings-manager.ts`: `migrateSettings` now rewrites every provider-keyed settings field from the legacy subscription ids to the canonical ones on first parse - the settings block key (`claudeSdkOauthProvider` -> `anthropicSubscriptionProvider`, `openaiCodexProvider` -> `chatgptSubscriptionProvider`), `defaultProvider`, the provider prefix of `defaultModel`, every `favoriteModels` entry, the `${provider}/${id}` keys of `modelThinkingLevels`/`modelServiceTiers`/`modelLastOnThinkingLevels`, and every `retry.fallbackChains` key plus the providers named inside each rung. Driven by the shared `normalizeProviderId`/`normalizeModelRef` helpers so no second map drifts. +- `packages/coding-agent/src/core/settings-manager.ts`: `migrateSettings` now rewrites every provider-keyed settings field from the legacy subscription ids to the canonical ones on first parse - the settings block key (`claudeSdkOauthProvider` -> `anthropicSubscriptionProvider`, `chatgptSubscriptionProvider` -> `chatgptSubscriptionProvider`), `defaultProvider`, the provider prefix of `defaultModel`, every `favoriteModels` entry, the `${provider}/${id}` keys of `modelThinkingLevels`/`modelServiceTiers`/`modelLastOnThinkingLevels`, and every `retry.fallbackChains` key plus the providers named inside each rung. Driven by the shared `normalizeProviderId`/`normalizeModelRef` helpers so no second map drifts. ### Why @@ -1258,12 +1258,12 @@ runtime it registered into is private or shared. ### What changed -- `packages/coding-agent/src/core/agent-session.ts`: `_isClaudeSdkSameModelRemintError` matches ONLY the Claude SDK lane's session-lock and bare `invalid_request` quirks (`this.model?.provider === CLAUDE_SDK_OAUTH_PROVIDER_ID`); the provider-agnostic stream-stall watchdog class is no longer swallowed, so a stall for ANY provider consumes the ordinary shared same-model budget and escalates to the fallback chain exactly as before. The auth-miss exclusion is `_isClaudeSdkAuthMissError`, an exact-message, Claude-lane-scoped check built on the shared `providerNotConfiguredMessage()` helper, so another provider's auth miss still hops the configured fallback chain. Supersedes the 2026-09-02 "Keep Claude SDK stalls and invalid_request on the same model" entry above. +- `packages/coding-agent/src/core/agent-session.ts`: `_isClaudeSdkSameModelRemintError` matches ONLY the Claude SDK lane's session-lock and bare `invalid_request` quirks (`this.model?.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID`); the provider-agnostic stream-stall watchdog class is no longer swallowed, so a stall for ANY provider consumes the ordinary shared same-model budget and escalates to the fallback chain exactly as before. The auth-miss exclusion is `_isClaudeSdkAuthMissError`, an exact-message, Claude-lane-scoped check built on the shared `providerNotConfiguredMessage()` helper, so another provider's auth miss still hops the configured fallback chain. Supersedes the 2026-09-02 "Keep Claude SDK stalls and invalid_request on the same model" entry above. - `packages/ai/src/auth/resolve.ts` + `packages/ai/src/models.ts`: `PROVIDER_NOT_CONFIGURED_PREFIX` / `providerNotConfiguredMessage()` export the exact auth-miss wording; `packages/coding-agent/src/core/model-runtime.ts` throws through the helper instead of its own literal, so the session-layer guard can never drift from the throw sites (the `TURN_RETRY_SUPPRESSION_PREFIX` pattern). -- `packages/coding-agent/src/core/auth-storage.ts`: every auth.json parse drops pool slots whose `access`/`refresh` equal the provider's `-managed` sentinel and clears a pin naming one; the mutable store writes the repair back once inside its existing lock. `packages/coding-agent/src/core/credential-pool/classify.ts` classifies that auth miss as `failover`/`auth_error` so one bad slot can never dead-end a pool. `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts` never lists a sentinel-material slot as an account. +- `packages/coding-agent/src/core/auth-storage.ts`: every auth.json parse drops pool slots whose `access`/`refresh` equal the provider's `-managed` sentinel and clears a pin naming one; the mutable store writes the repair back once inside its existing lock. `packages/coding-agent/src/core/credential-pool/classify.ts` classifies that auth miss as `failover`/`auth_error` so one bad slot can never dead-end a pool. `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts` never lists a sentinel-material slot as an account. - `packages/ai/src/auth/pool/slots.ts`: `appendLoginSlot` MERGES a provider-owned pool onto the value read under the credential lock (stored slots and their block state win; only genuinely new names are appended) instead of whole-writing a snapshot read before the browser round trip; `managedSentinelMaterial` / `isManagedSentinelSlot` / `repairManagedSentinelSlots` implement the repair algebra. A flat `current` keeps the whole-write shape because the provider's accounts already carry this login. - `packages/coding-agent/src/modes/interactive/components/login-dialog.ts`: every `(to cancel)` / `(to close)` hint row routes through one tracked live hint, so `showWaiting` / `showInfo` replace a previous hint instead of painting beside it, and content-clearing paths reset the tracked hint. -- Tests: `test/suite/retry-fallback-hard-error.test.ts` (Claude-lane tests run under a `claude-sdk-oauth` faux provider, plus new guards proving a non-Claude `invalid_request` and a non-Claude auth miss still hop the chain), `test/auth-storage.test.ts`, `test/credential-error-taxonomy.test.ts`, `test/model-runtime-credential-rotation.test.ts`, `test/claude-sdk-oauth-accounts.test.ts`, `packages/ai/test/credential-pool-mutations.test.ts`, `test/suite/regressions/5433-extension-oauth-prompt-input.test.ts` (the bare-`>` line was a tautology; it now asserts exactly one live `>` row and one live hint row). +- Tests: `test/suite/retry-fallback-hard-error.test.ts` (Claude-lane tests run under a `claude-sdk-oauth` faux provider, plus new guards proving a non-Claude `invalid_request` and a non-Claude auth miss still hop the chain), `test/auth-storage.test.ts`, `test/credential-error-taxonomy.test.ts`, `test/model-runtime-credential-rotation.test.ts`, `test/anthropic-subscription-accounts.test.ts`, `packages/ai/test/credential-pool-mutations.test.ts`, `test/suite/regressions/5433-extension-oauth-prompt-input.test.ts` (the bare-`>` line was a tautology; it now asserts exactly one live `>` row and one live hint row). ### Why @@ -1366,7 +1366,7 @@ runtime it registered into is private or shared. ### Why -- Auth wiring failures were classified as hard-error and immediately switched `claude-sdk-oauth/claude-opus-5` onto a different provider (for example `opengateway/anthropic/claude-opus-5`) instead of staying on Claude SDK OAuth or its sibling accounts. +- Auth wiring failures were classified as hard-error and immediately switched `anthropic-subscription/claude-opus-5` onto a different provider (for example `opengateway/anthropic/claude-opus-5`) instead of staying on Claude SDK OAuth or its sibling accounts. ### Why an extension could not handle it @@ -1994,7 +1994,7 @@ runtime it registered into is private or shared. - `packages/coding-agent/src/core/messages.ts`: `convertToLlm` runs the shared `dropFailedAssistantTurns` from `@earendil-works/pi-ai` as its final step, removing assistant turns with `stopReason` `error`/`aborted` and the tool results orphaned by that drop from the returned `Message[]`. `convertToLlmForTransport` inherits the drop through `convertToLlm`. - `packages/coding-agent/test/convert-to-llm-drops-failed-turns.test.ts` (new): `convertToLlm` on `[user, assistant(error, "PARTIAL" + toolCall), toolResult, user]` yields no `PARTIAL` text and no orphaned tool call; same for an aborted turn. -- `packages/coding-agent/test/claude-sdk-oauth-prompt-bridge.test.ts`: regression pinning that `buildPromptBlocks` over `convertToLlm`-processed history renders no failed-turn text and no orphaned tool call id. +- `packages/coding-agent/test/anthropic-subscription-prompt-bridge.test.ts`: regression pinning that `buildPromptBlocks` over `convertToLlm`-processed history renders no failed-turn text and no orphaned tool call id. ### Why @@ -2007,7 +2007,7 @@ runtime it registered into is private or shared. ### Expected merge conflict zones - LOW: the tail of `convertToLlm` in `packages/coding-agent/src/core/messages.ts` (the new `dropFailedAssistantTurns` return). -- LOW: `packages/coding-agent/test/claude-sdk-oauth-prompt-bridge.test.ts` (one new `it` before the stream test). +- LOW: `packages/coding-agent/test/anthropic-subscription-prompt-bridge.test.ts` (one new `it` before the stream test). ## 2026-09-04 - Restore the selected model, not its upstream wire id, on resume diff --git a/packages/coding-agent/src/core/credential-accounts.ts b/packages/coding-agent/src/core/credential-accounts.ts index 58187eae8d..573df736a6 100644 --- a/packages/coding-agent/src/core/credential-accounts.ts +++ b/packages/coding-agent/src/core/credential-accounts.ts @@ -11,8 +11,8 @@ import { import type { AuthStorage } from "./auth-storage.ts"; import { discoverEnvSlots } from "./credential-pool/env-slots.ts"; import { CredentialSlotRepository, type CredentialSlotState, slotHealth } from "./credential-pool/state-store.ts"; -import { emitProviderAccountsChanged } from "./extensions/builtin/claude-sdk-oauth/account-events.ts"; -import { SENTINEL_OAUTH_FIELDS } from "./extensions/builtin/claude-sdk-oauth/accounts.ts"; +import { emitProviderAccountsChanged } from "./extensions/builtin/anthropic-subscription/account-events.ts"; +import { SENTINEL_OAUTH_FIELDS } from "./extensions/builtin/anthropic-subscription/accounts.ts"; export type CredentialAccountSource = "login" | "import" | "env"; diff --git a/packages/coding-agent/src/core/extensions/builtin/AGENTS.md b/packages/coding-agent/src/core/extensions/builtin/AGENTS.md index ccfb5c26e3..1a9745e1c6 100644 --- a/packages/coding-agent/src/core/extensions/builtin/AGENTS.md +++ b/packages/coding-agent/src/core/extensions/builtin/AGENTS.md @@ -40,7 +40,7 @@ | 32 | `cache-keepalive` | `cache-keepalive/` | Warms the provider prompt cache between turns (`warmPromptCache`, Anthropic-aware TTL) and renders a `cache-keepalive` notice entry | | 33 | `ttsr` | `ttsr/` | Stream-rule detection (collapse + control-token-leak) with abort→remediate→retry; ported from oh-my-pi — see `ttsr/changes.md` | | 34 | `btw` | `btw/` | `/btw` side-question command that queries in parallel without touching the main session | -| 35 | `claude-sdk-oauth` | `claude-sdk-oauth/` | Claude SDK OAuth provider: multi-account OAuth, resume-first session continuity, stream-safe failover — see `claude-sdk-oauth/AGENTS.md` + `changes.md` | +| 35 | `claude-sdk-oauth` | `anthropic-subscription/` | Claude SDK OAuth provider: multi-account OAuth, resume-first session continuity, stream-safe failover — see `anthropic-subscription/AGENTS.md` + `changes.md` | | 36 | `cursor-cli-oauth` | `cursor-cli-oauth/` | Cursor CLI OAuth provider lane: multi-account OAuth, spawn/stream parsing, failover; registers unconditionally and reports executable/auth state through its oauth check — see `cursor-cli-oauth/AGENTS.md` | | 37 | `config-reload` | `config-reload/` | Hash-gated watcher for trusted global/project config surfaces that defers a full session reload until idle and exposes the `config-watch:*` event protocol; registered after settings-dependent builtins so a reload rebuilds their resolved settings, and before final MCP observation | | 38 | `tool-search` | `tool-search/` | Shared tool catalog + `tool_search` exposure tool; loads before MCP, which feeds its tools into the same catalog | @@ -74,7 +74,7 @@ Plus bundled extension **codemode** (`@code-yeongyu/senpi-codemode`, resolved by ## NOTES - MCP search exposure tool is `tool_search`, owned by the registered `tool-search` builtin (`builtin/tool-search/tool.ts`). Do not reintroduce `mcp_search` references anywhere. -- Sub-directory detail lives in per-extension `AGENTS.md` files (compaction, mcp, goal, loop, terminal, permission-system, prompt-preset, gpt-apply-patch, claude-sdk-oauth, cursor-cli-oauth). +- Sub-directory detail lives in per-extension `AGENTS.md` files (compaction, mcp, goal, loop, terminal, permission-system, prompt-preset, gpt-apply-patch, anthropic-subscription, cursor-cli-oauth). --- Generated: 2026-08-22 | Commit: `a5eed4453` diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/AGENTS.md b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/AGENTS.md similarity index 92% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/AGENTS.md rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/AGENTS.md index 39d9f05c1d..1a478de1d5 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/AGENTS.md +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/AGENTS.md @@ -1,4 +1,4 @@ -# claude-sdk-oauth +# anthropic-subscription Claude SDK OAuth provider extension. Registers a builtin provider that runs turns through the `@anthropic-ai/claude-agent-sdk` subprocess with native multi-account OAuth, HRW session affinity, stream-safe account failover, and resume-first session continuity. Renamed from `claude-agent-sdk` on 2026-07-31, then to the provider id `anthropic-subscription` on 2026-09-22 (senpi#1989). Unlike the first rename, this one IS backward compatible: legacy provider ids are normalized at every read boundary, `auth.json` and `settings.json` are migrated once, and the per-account directory is moved once, so an upgrading user keeps their login, default model and saved accounts. A legacy id the user TYPES is still rejected, with the new id named. @@ -25,7 +25,7 @@ Generated: 2026-08-07 | Commit: `4f26b8282` | `prompt-directive-dedupe.ts` | `dedupeUltraworkBlocks`: collapses repeated `` spans in flatten output; never mutates `context.messages` | | `custom-tools.ts` | Senpi tools exposed as an SDK MCP server; execution denied SDK-side (`denyCustomToolExecution`), executed by senpi | | `sdk-boundary.ts` | Single import boundary over `@anthropic-ai/claude-agent-sdk` (`query`, `createSdkMcpServer`, types) | -| `options.ts` | `buildClaudeSdkOauthQueryOptions`: settings + `SENPI_CLAUDE_SDK_OAUTH_*` env resolution, append assembly | +| `options.ts` | `buildAnthropicSubscriptionQueryOptions`: settings + `SENPI_CLAUDE_SDK_OAUTH_*` env resolution, append assembly | | `executable.ts` | Claude Code executable resolution | | `changes.md` | Fork-change record; read before touching anything here | @@ -48,7 +48,7 @@ Generated: 2026-08-07 | Commit: `4f26b8282` ## TESTS -Flat cluster at `test/claude-sdk-oauth-*.test.ts` (51 files): accounts, affinity, auth-lane, binding, continuity decisions, failover, custom-tools schema, guidance, login, model switch, observability, and more. Keep edited test files below the 250-pure-LOC ceiling (see 2026-07-31 rename entry). +Flat cluster at `test/anthropic-subscription-*.test.ts` (renamed from `claude-sdk-oauth-*.test.ts` in the 2026-09-22 internal rename): accounts, affinity, auth-lane, binding, continuity decisions, failover, custom-tools schema, guidance, login, model switch, observability, and more. Keep edited test files below the 250-pure-LOC ceiling (see 2026-07-31 rename entry). ## MERGE RISK diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts similarity index 83% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts index 91dac8c427..fdbf92e56c 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts @@ -4,26 +4,29 @@ import type { ExtensionAPI, ExtensionCommandContext } from "../../types.ts"; import { accountDisplayNameCommand, promptAccountDisplayName } from "../account-display-name.ts"; import { createExtensionLoginInteraction, LOGIN_CANCELLED_MESSAGE } from "../oauth-login-interaction.ts"; import { emitProviderAccountsChanged } from "./account-events.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID, pinProviderAccount, removeProviderAccount } from "./account-management.ts"; -import { type AccountSlot, type ClaudeSdkOauthCredential, emptyCredential, listAccounts } from "./accounts.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, pinProviderAccount, removeProviderAccount } from "./account-management.ts"; +import { type AccountSlot, type AnthropicSubscriptionCredential, emptyCredential, listAccounts } from "./accounts.ts"; import { selectAccount } from "./affinity.ts"; -import { type ClaudeSdkOauthProviderSettings, loadClaudeSdkOauthProviderSettingsFromDisk } from "./settings.ts"; +import { + type AnthropicSubscriptionProviderSettings, + loadAnthropicSubscriptionProviderSettingsFromDisk, +} from "./settings.ts"; const cliPinsBySession = new Map(); type CommandEnvironment = (name: string) => string | undefined; export interface ClaudeAccountCommandDeps { - loadSettings?: (cwd: string) => ClaudeSdkOauthProviderSettings; + loadSettings?: (cwd: string) => AnthropicSubscriptionProviderSettings; environment?: CommandEnvironment; /** Browser launcher for the OAuth authorize URL; tests inject a recorder. */ openBrowser?: ((url: string) => void) | undefined; } -const CLAUDE_SDK_OAUTH_PROVIDER_LABEL = "Anthropic Subscription"; +const ANTHROPIC_SUBSCRIPTION_PROVIDER_LABEL = "Anthropic Subscription"; -function asCredential(value: Credential | undefined): ClaudeSdkOauthCredential | undefined { - return value?.type === "oauth" ? (value as ClaudeSdkOauthCredential) : undefined; +function asCredential(value: Credential | undefined): AnthropicSubscriptionCredential | undefined { + return value?.type === "oauth" ? (value as AnthropicSubscriptionCredential) : undefined; } function slotStatus(slot: AccountSlot): string { @@ -38,10 +41,10 @@ function readAccounts( ctx: ExtensionCommandContext, environment: CommandEnvironment, ): { - credential: ClaudeSdkOauthCredential | undefined; + credential: AnthropicSubscriptionCredential | undefined; accounts: AccountSlot[]; } { - const credential = asCredential(ctx.modelRegistry.authStorage.get(CLAUDE_SDK_OAUTH_PROVIDER_ID)); + const credential = asCredential(ctx.modelRegistry.authStorage.get(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID)); return { credential, accounts: listAccounts(credential ?? emptyCredential(), environment), @@ -70,7 +73,7 @@ export function resolveClaudeAccountPin( } export function registerClaudeAccountCommand(pi: ExtensionAPI, deps: ClaudeAccountCommandDeps = {}): void { - const loadSettings = deps.loadSettings ?? loadClaudeSdkOauthProviderSettingsFromDisk; + const loadSettings = deps.loadSettings ?? loadAnthropicSubscriptionProviderSettingsFromDisk; const environment = deps.environment ?? ((name: string) => process.env[name]); pi.registerFlag("claude-account", { @@ -90,7 +93,7 @@ export function registerClaudeAccountCommand(pi: ExtensionAPI, deps: ClaudeAccou description: "List and manage Anthropic Subscription accounts.", argumentHint: "[add | remove | pin | unpin | rename | clear-name ]", handler: async (rawArgs, ctx) => { - if (await accountDisplayNameCommand(ctx, CLAUDE_SDK_OAUTH_PROVIDER_ID, rawArgs)) return; + if (await accountDisplayNameCommand(ctx, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, rawArgs)) return; const args = parseArgs(rawArgs); const action = args[0] ?? "list"; if (action === "list") { @@ -123,7 +126,7 @@ export function registerClaudeAccountCommand(pi: ExtensionAPI, deps: ClaudeAccou function showAccounts( ctx: ExtensionCommandContext, - settings: ClaudeSdkOauthProviderSettings, + settings: AnthropicSubscriptionProviderSettings, environment: CommandEnvironment, ): void { const { credential, accounts } = readAccounts(ctx, environment); @@ -165,16 +168,16 @@ async function addAccount(ctx: ExtensionCommandContext, deps: ClaudeAccountComma } try { let receipt: AccountLoginReceipt | undefined; - await ctx.modelRegistry.modelRuntime.login(CLAUDE_SDK_OAUTH_PROVIDER_ID, "oauth", { + await ctx.modelRegistry.modelRuntime.login(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, "oauth", { ...createExtensionLoginInteraction(ctx, { - providerLabel: CLAUDE_SDK_OAUTH_PROVIDER_LABEL, + providerLabel: ANTHROPIC_SUBSCRIPTION_PROVIDER_LABEL, openBrowser: deps.openBrowser, }), onAccountCommitted: (committed) => { receipt = committed; }, }); - emitProviderAccountsChanged(CLAUDE_SDK_OAUTH_PROVIDER_ID); + emitProviderAccountsChanged(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID); ctx.ui.notify("Anthropic Subscription account added.", "info"); await promptAccountDisplayName(ctx, receipt); } catch (error) { @@ -205,7 +208,7 @@ async function removeNamedAccount( return; } try { - await removeProviderAccount(ctx.modelRegistry.authStorage, CLAUDE_SDK_OAUTH_PROVIDER_ID, name); + await removeProviderAccount(ctx.modelRegistry.authStorage, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, name); ctx.ui.notify(`Removed Anthropic Subscription account: ${name}.`, "info"); } catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); @@ -225,7 +228,7 @@ async function pinNamedAccount( return accountError(ctx, name); } try { - await pinProviderAccount(ctx.modelRegistry.authStorage, CLAUDE_SDK_OAUTH_PROVIDER_ID, name); + await pinProviderAccount(ctx.modelRegistry.authStorage, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, name); ctx.ui.notify(`Pinned Anthropic Subscription account: ${name}.`, "info"); } catch (error) { ctx.ui.notify(error instanceof Error ? error.message : String(error), "error"); @@ -233,11 +236,11 @@ async function pinNamedAccount( } async function unpinAccount(ctx: ExtensionCommandContext): Promise { - const credential = asCredential(ctx.modelRegistry.authStorage.get(CLAUDE_SDK_OAUTH_PROVIDER_ID)); + const credential = asCredential(ctx.modelRegistry.authStorage.get(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID)); if (!credential?.pinned) { ctx.ui.notify("No stored Anthropic Subscription account pin is set.", "info"); return; } - await pinProviderAccount(ctx.modelRegistry.authStorage, CLAUDE_SDK_OAUTH_PROVIDER_ID, null); + await pinProviderAccount(ctx.modelRegistry.authStorage, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, null); ctx.ui.notify("Unpinned Anthropic Subscription account.", "info"); } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-events.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-events.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-events.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-events.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts similarity index 90% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts index a6b26cb1bc..f002e84c9c 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts @@ -4,14 +4,14 @@ import type { AuthStorage } from "../../../auth-storage.ts"; import { emitProviderAccountsChanged } from "./account-events.ts"; import { type AccountSlot, - type ClaudeSdkOauthCredential, + type AnthropicSubscriptionCredential, emptyCredential, listAccounts, pinAccount, removeAccount, } from "./accounts.ts"; -export const CLAUDE_SDK_OAUTH_PROVIDER_ID = "anthropic-subscription"; +export const ANTHROPIC_SUBSCRIPTION_PROVIDER_ID = "anthropic-subscription"; export type ProviderAccountSummary = { readonly name: string; @@ -84,13 +84,13 @@ export async function removeProviderAccount(storage: AuthStorage, provider: stri } function assertManagedProvider(provider: string): void { - if (provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) { + if (provider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) { throw new Error(`Provider account management is unavailable for: ${provider}`); } } -function credentialFrom(credential: Credential | undefined): ClaudeSdkOauthCredential { +function credentialFrom(credential: Credential | undefined): AnthropicSubscriptionCredential { if (credential === undefined) return emptyCredential(); if (credential.type !== "oauth") throw new Error("Provider account management requires an OAuth credential"); - return credential as ClaudeSdkOauthCredential; + return credential as AnthropicSubscriptionCredential; } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts similarity index 84% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts index 69c3776ebd..f3bdcc5448 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts @@ -14,7 +14,7 @@ export type AccountSlot = { export type SlotState = Record; -export type ClaudeSdkOauthCredential = OAuthCredential & { +export type AnthropicSubscriptionCredential = OAuthCredential & { accounts?: AccountSlot[]; pinned?: string; slotState?: SlotState; @@ -26,11 +26,11 @@ export const SENTINEL_OAUTH_FIELDS = { expires: 4102444800000, } as const; -export function emptyCredential(): ClaudeSdkOauthCredential { +export function emptyCredential(): AnthropicSubscriptionCredential { return { type: "oauth", ...SENTINEL_OAUTH_FIELDS, accounts: [] }; } -function storedSlots(credential: ClaudeSdkOauthCredential): AccountSlot[] { +function storedSlots(credential: AnthropicSubscriptionCredential): AccountSlot[] { return credential.accounts ?? []; } @@ -45,7 +45,7 @@ export function isSentinelSlot(slot: Pick): b } export function listAccounts( - credential: ClaudeSdkOauthCredential, + credential: AnthropicSubscriptionCredential, env?: (name: string) => string | undefined, ): AccountSlot[] { const slots = storedSlots(credential).filter((slot) => !isSentinelSlot(slot)); @@ -69,7 +69,10 @@ export function assertValidAccountName(name: string): void { } } -export function addAccount(credential: ClaudeSdkOauthCredential, slot: AccountSlot): ClaudeSdkOauthCredential { +export function addAccount( + credential: AnthropicSubscriptionCredential, + slot: AccountSlot, +): AnthropicSubscriptionCredential { assertValidAccountName(slot.name); if (storedSlots(credential).some((existing) => existing.name === slot.name)) { throw new Error(`Account '${slot.name}' already exists`); @@ -82,7 +85,10 @@ export function addAccount(credential: ClaudeSdkOauthCredential, slot: AccountSl * token material and source, block stamps cleared, displayName preserved — so * a successful login lifts the slot's `auth_error` lock. Unknown names append. */ -export function upsertAccount(credential: ClaudeSdkOauthCredential, slot: AccountSlot): ClaudeSdkOauthCredential { +export function upsertAccount( + credential: AnthropicSubscriptionCredential, + slot: AccountSlot, +): AnthropicSubscriptionCredential { assertValidAccountName(slot.name); const existing = storedSlots(credential).find((candidate) => candidate.name === slot.name); if (!existing) return { ...credential, accounts: [...storedSlots(credential), slot] }; @@ -100,18 +106,21 @@ export function upsertAccount(credential: ClaudeSdkOauthCredential, slot: Accoun }; } -export function removeAccount(credential: ClaudeSdkOauthCredential, name: string): ClaudeSdkOauthCredential { +export function removeAccount( + credential: AnthropicSubscriptionCredential, + name: string, +): AnthropicSubscriptionCredential { const accounts = storedSlots(credential).filter((slot) => slot.name !== name); - const next: ClaudeSdkOauthCredential = { ...credential, accounts }; + const next: AnthropicSubscriptionCredential = { ...credential, accounts }; if (credential.pinned === name) delete next.pinned; return next; } -export function pinAccount(credential: ClaudeSdkOauthCredential, name: string): ClaudeSdkOauthCredential { +export function pinAccount(credential: AnthropicSubscriptionCredential, name: string): AnthropicSubscriptionCredential { return { ...credential, pinned: name }; } -export function assertSentinelInvariant(credential: ClaudeSdkOauthCredential): void { +export function assertSentinelInvariant(credential: AnthropicSubscriptionCredential): void { if ( credential.access !== SENTINEL_OAUTH_FIELDS.access || credential.refresh !== SENTINEL_OAUTH_FIELDS.refresh || @@ -159,7 +168,7 @@ export async function refreshSlot( ): Promise { return store.modify(providerId, async (current) => { if (current?.type !== "oauth") return undefined; - const credential = current as ClaudeSdkOauthCredential; + const credential = current as AnthropicSubscriptionCredential; const slot = storedSlots(credential).find((candidate) => candidate.name === slotName); if (!slot) return current; if (!isExpiring(slot.expires)) return current; diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/affinity.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/affinity.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/affinity.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/affinity.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/api-id.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/api-id.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/api-id.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/api-id.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-attempt.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-attempt.ts similarity index 92% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-attempt.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-attempt.ts index 48951f0f8a..1485c3183a 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-attempt.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-attempt.ts @@ -1,11 +1,11 @@ import type { AccountSlot } from "./accounts.ts"; import type { Options, SDKMessage, SdkQuery } from "./sdk-boundary.ts"; -import type { ClaudeSdkOauthTokenInjection } from "./settings.ts"; +import type { AnthropicSubscriptionTokenInjection } from "./settings.ts"; export type AuthenticatedAttemptInput = { accountName: string; accounts: readonly AccountSlot[]; - authLane: ClaudeSdkOauthTokenInjection; + authLane: AnthropicSubscriptionTokenInjection; options: Options; }; diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-environment.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-environment.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-environment.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-environment.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-lane.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-lane.ts similarity index 86% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-lane.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-lane.ts index 3147287f9c..aafceb7da6 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-lane.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-lane.ts @@ -3,10 +3,10 @@ import { loadAnthropicOAuth } from "@earendil-works/pi-ai/oauth"; import { getAgentDir } from "../../../../config.ts"; import { AuthStorage } from "../../../auth-storage.ts"; import { emitProviderAccountFailover, emitProviderAccountsChanged } from "./account-events.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; import { type AccountSlot, - type ClaudeSdkOauthCredential, + type AnthropicSubscriptionCredential, emptyCredential, envSlotToken, listAccounts, @@ -21,9 +21,9 @@ import { classifySdkError, sdkAssistantFailure, sdkResultFailure } from "./error import { runFailover } from "./failover.ts"; import { refusalError } from "./refusal.ts"; import type { Options, SDKMessage, SdkQuery } from "./sdk-boundary.ts"; -import type { ClaudeSdkOauthProviderSettings, ClaudeSdkOauthTokenInjection } from "./settings.ts"; +import type { AnthropicSubscriptionProviderSettings, AnthropicSubscriptionTokenInjection } from "./settings.ts"; -export { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; +export { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; export const EXPIRING_WITHIN_MS = 5 * 60_000; @@ -68,8 +68,8 @@ export type { AuthenticatedAttemptInput } from "./auth-attempt.ts"; export type AuthenticatedQueryInput = { prompt: Parameters[0]["prompt"]; query: SdkQuery; - buildOptions: (lane: ClaudeSdkOauthTokenInjection) => Options; - providerSettings: ClaudeSdkOauthProviderSettings; + buildOptions: (lane: AnthropicSubscriptionTokenInjection) => Options; + providerSettings: AnthropicSubscriptionProviderSettings; /** Effective request auth environment; overrides the host for account discovery and SDK spawn. */ env?: Record; signal?: AbortSignal; @@ -85,33 +85,33 @@ export type AuthenticatedQueryInput = { type ManagedPool = { accounts: AccountSlot[]; environment: NodeJS.ProcessEnv; - lane: Exclude; + lane: Exclude; pinnedAccount?: string; store: CredentialStore; }; export function resolveEffectiveLane( - settings: ClaudeSdkOauthProviderSettings, + settings: AnthropicSubscriptionProviderSettings, accounts: readonly AccountSlot[], -): ClaudeSdkOauthTokenInjection { +): AnthropicSubscriptionTokenInjection { return settings.tokenInjection ?? (accounts.length > 0 ? "oauth-slots" : "ambient"); } async function managedPool( - settings: ClaudeSdkOauthProviderSettings, + settings: AnthropicSubscriptionProviderSettings, requestEnvironment?: Record, ): Promise { const store = activeBoundary.createStore(); - let credential = await store.read(CLAUDE_SDK_OAUTH_PROVIDER_ID); + let credential = await store.read(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID); const environment = mergeRequestAuthEnvironment(activeBoundary.env(), requestEnvironment); let accounts = listAccounts( - (credential as ClaudeSdkOauthCredential | undefined) ?? emptyCredential(), + (credential as AnthropicSubscriptionCredential | undefined) ?? emptyCredential(), (name) => environment[name], ); if (!credential && accounts.length > 0) { - credential = await store.modify(CLAUDE_SDK_OAUTH_PROVIDER_ID, async () => emptyCredential()); + credential = await store.modify(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, async () => emptyCredential()); accounts = listAccounts( - (credential as ClaudeSdkOauthCredential) ?? emptyCredential(), + (credential as AnthropicSubscriptionCredential) ?? emptyCredential(), (name) => environment[name], ); } @@ -120,7 +120,7 @@ async function managedPool( configuredLane === "config-dir" && hasRequestOauthToken(requestEnvironment) ? "oauth-slots" : configuredLane; if (lane === "ambient") return undefined; if (accounts.length === 0) throw new Error(NO_MANAGED_ACCOUNTS_ERROR); - const stored = credential?.type === "oauth" ? (credential as ClaudeSdkOauthCredential) : undefined; + const stored = credential?.type === "oauth" ? (credential as AnthropicSubscriptionCredential) : undefined; return { accounts, environment, lane, pinnedAccount: settings.pinnedAccount ?? stored?.pinned, store }; } @@ -135,13 +135,13 @@ async function prepareSlot( try { const refreshed = await refreshSlot( pool.store, - CLAUDE_SDK_OAUTH_PROVIDER_ID, + ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, slot.name, activeBoundary.refresher, signal, (expires) => activeBoundary.now() >= expires - EXPIRING_WITHIN_MS, ); - const credential = refreshed?.type === "oauth" ? (refreshed as ClaudeSdkOauthCredential) : undefined; + const credential = refreshed?.type === "oauth" ? (refreshed as AnthropicSubscriptionCredential) : undefined; const updated = listAccounts(credential ?? emptyCredential(), (name) => environment[name]).find( (candidate) => candidate.name === slot.name, ); @@ -219,15 +219,15 @@ export async function* queryWithAuthLane(input: AuthenticatedQueryInput): AsyncG }, classify: classifySdkError, store: pool.store, - providerId: CLAUDE_SDK_OAUTH_PROVIDER_ID, + providerId: ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, now: activeBoundary.now, errorFromEvent: sdkFailure, isVisibleDelta: visibleSdkMessage, onFailover: ({ account, nextAccount, classification }) => { - emitProviderAccountsChanged(CLAUDE_SDK_OAUTH_PROVIDER_ID); + emitProviderAccountsChanged(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID); if (nextAccount) { emitProviderAccountFailover( - CLAUDE_SDK_OAUTH_PROVIDER_ID, + ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, account.name, nextAccount.name, classification.kind, diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/availability.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/availability.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/availability.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/availability.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/bounded-queue.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/bounded-queue.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/bounded-queue.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/bounded-queue.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/changes.md b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/changes.md similarity index 84% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/changes.md rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/changes.md index 7f68c4eceb..9299bb8edc 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/changes.md +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/changes.md @@ -1,3 +1,24 @@ +## 2026-09-22 - rename internal symbols, files and the extension directory (senpi#1989) + +### What changed + +- The extension directory moved `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/` -> `.../anthropic-subscription/` (`git mv`, history preserved), and the identifier family renamed to the new spelling: `CLAUDE_SDK_OAUTH_*` -> `ANTHROPIC_SUBSCRIPTION_*`, `ClaudeSdkOauth*` -> `AnthropicSubscription*`, `claudeSdkOauth*` -> `anthropicSubscription*` (e.g. `ANTHROPIC_SUBSCRIPTION_PROVIDER_ID`, `loadAnthropicSubscriptionProviderSettings`, `buildAnthropicSubscriptionQueryOptions`). The matching test cluster (`test/claude-sdk-oauth-*.test.ts` -> `test/anthropic-subscription-*.test.ts`), helpers and `.agents/skills/senpi-qa/scripts/claude-sdk-oauth-*` were renamed likewise, and every importer updated. Behaviour-free: no string literal changed. + +### Why + +Completion of the provider rename (senpi#1989): after the provider id became `anthropic-subscription`, the internal spellings still named an implementation detail that no longer exists, so every grep for the provider surfaced internals that name nothing real. Deliberately its own commit so review can separate the mechanical rename from the semantic changes. + +### Why an extension could not handle it + +This IS the extension: the directory name, its import graph and its symbol names are the thing renamed. + +### Expected merge conflict zones + +- Every file in this directory and every importer of `extensions/builtin/anthropic-subscription/*`: `src/core/agent-session.ts`, `src/core/credential-accounts.ts`, `src/modes/rpc/connection-handler.ts`, `src/modes/app-server/server/account.ts`, `src/modes/app-server/server/server-core.ts`, `src/core/extensions/builtin/index.ts`, `.../builtin/compaction/lane-policy.ts`, `.../builtin/gpt-account.ts`, `.../builtin/cursor-cli-oauth/account-command.ts`. +- The renamed test cluster under `packages/coding-agent/test/` and the `.agents/skills/senpi-qa/scripts/` probes; PRs touching the old paths must rebase onto this rename. + +Frozen on purpose (byte-identical values): `ANTHROPIC_SUBSCRIPTION_COMPACT_ENTRY_TYPE` is still `"claude-sdk-oauth-compact"`, the boundary schema still `senpi.claude-sdk-oauth.compact-boundary.v1`, the sidecar still `.claude-sdk-oauth-binding.json`, the tool-watch type still `claude-sdk-oauth-tool-watch`, the managed sentinel still `claude-sdk-oauth-managed`, every `claude_sdk_oauth_*` diagnostic id, every `SENPI_CLAUDE_SDK_OAUTH_*` env-var name, `CLAUDE_CODE_OAUTH_TOKEN*`, the wire api id `CLAUDE_SDK_OAUTH_API_ID` (`"claude-sdk-oauth"`), and the legacy settings-read property `claudeSdkOauthProvider` (the two-release compatibility window). + ## 2026-09-22 - multi-binary coexistence pinned for the subscription rename (senpi#1989) ### What changed @@ -20,8 +41,8 @@ The surfaces under test are core credential, settings, model-config and account- ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts`: `readStoredCredential` reads auth.json through the canonical key and then the legacy spelling. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tool-watch.ts`: the provider comparison normalizes `ctx.model?.provider` before comparing. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts`: `readStoredCredential` reads auth.json through the canonical key and then the legacy spelling. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tool-watch.ts`: the provider comparison normalizes `ctx.model?.provider` before comparing. ### Why @@ -33,14 +54,14 @@ This IS the provider extension's own code; the raw read runs inside its oauth co ### Expected merge conflict zones -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts` `readStoredCredential`. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tool-watch.ts` the constant block and the `tool_execution_end` guard. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts` `readStoredCredential`. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tool-watch.ts` the constant block and the `tool_execution_end` guard. ## 2026-09-22 - read the renamed provider settings block (senpi#1989) ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/settings.ts`: `loadClaudeSdkOauthProviderSettings` now reads the canonical `anthropicSubscriptionProvider` block, falling back to the legacy `claudeSdkOauthProvider` key; the `SettingsWithClaudeSdkOauthProvider` type carries both. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/settings.ts`: `loadAnthropicSubscriptionProviderSettings` now reads the canonical `anthropicSubscriptionProvider` block, falling back to the legacy `claudeSdkOauthProvider` key; the `SettingsWithAnthropicSubscriptionProvider` type carries both. ### Why @@ -52,13 +73,13 @@ This IS the provider extension's own settings reader; the migration happens in c ### Expected merge conflict zones -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/settings.ts` `loadClaudeSdkOauthProviderSettings` and the `SettingsWithClaudeSdkOauthProvider` type. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/settings.ts` `loadAnthropicSubscriptionProviderSettings` and the `SettingsWithAnthropicSubscriptionProvider` type. ## 2026-09-22 - move the per-account directory to the canonical name (senpi#1989) ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/config-dir-credentials.ts`: new `resolveAccountsDirectory` performs a one-shot, idempotent move of `/claude-sdk-oauth-accounts` -> `/anthropic-subscription-accounts` the first time the lane resolves an account dir; `writeConfigDirCredential` routes through it. Slot names and file contents are preserved byte-for-byte. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/config-dir-credentials.ts`: new `resolveAccountsDirectory` performs a one-shot, idempotent move of `/claude-sdk-oauth-accounts` -> `/anthropic-subscription-accounts` the first time the lane resolves an account dir; `writeConfigDirCredential` routes through it. Slot names and file contents are preserved byte-for-byte. ### Why @@ -70,22 +91,22 @@ The account directory is owned and written by this provider extension; the move ### Expected merge conflict zones -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/config-dir-credentials.ts` `resolveAccountsDirectory` and the accounts-dir name constants. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/config-dir-credentials.ts` `resolveAccountsDirectory` and the accounts-dir name constants. ## 2026-09-22 - claude-sdk-oauth provider id renamed to anthropic-subscription across the extension (senpi#1989) ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts`: `CLAUDE_SDK_OAUTH_PROVIDER_ID` value is `"anthropic-subscription"` (symbol name unchanged). -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts`: `isResidentAssistant` compares `message.api === CLAUDE_SDK_OAUTH_API_ID` and `message.provider === CLAUDE_SDK_OAUTH_PROVIDER_ID`. The old code compared BOTH fields to the provider-id constant, which only worked while api id === provider id; after the value rename it would always be false and the #7925 resident commit boundary would silently die. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tool-watch.ts`: runtime `PROVIDER_ID` comparison moves to the new id; `TOOL_WATCH_CUSTOM_TYPE` (`claude-sdk-oauth-tool-watch`) is a persisted session custom-entry type and stays. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/guidance.ts`: rendered guidance (`/login anthropic-subscription`) and the override-prompt error use the new id/label. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts`: login label is `"Anthropic Subscription (Claude Pro/Max)"`; auth-check source follows. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts`: label constant and every notify/description string read "Anthropic Subscription". -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/affinity.ts`, `bounded-queue.ts`, `failover.ts`, `session-stream.ts`, `session-registry.ts`, `session-registry-pump.ts`, `session-reattach.ts`, `session-turn-claim.ts`: user-visible error/notify strings use the new label. `DEFAULT_AFFINITY_KEY` (`claude-sdk-oauth-default`) is FROZEN — it is the HRW hash-domain constant; renaming it silently remaps every unpinned session to a different account. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-observability.ts`: the close-cause regex accepts BOTH phrasings (`Claude SDK OAuth query|Anthropic Subscription query`) so classification survives the renamed pump messages. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/auth-lane.ts`: the no-managed-accounts error names the new lane and `/login anthropic-subscription`; `CLAUDE_CODE_OAUTH_TOKEN` unchanged. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/errors.ts`: doc comment names the new label. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts`: `ANTHROPIC_SUBSCRIPTION_PROVIDER_ID` value is `"anthropic-subscription"` (symbol name unchanged). +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-commit-boundary.ts`: `isResidentAssistant` compares `message.api === CLAUDE_SDK_OAUTH_API_ID` and `message.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID`. The old code compared BOTH fields to the provider-id constant, which only worked while api id === provider id; after the value rename it would always be false and the #7925 resident commit boundary would silently die. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tool-watch.ts`: runtime `PROVIDER_ID` comparison moves to the new id; `TOOL_WATCH_CUSTOM_TYPE` (`claude-sdk-oauth-tool-watch`) is a persisted session custom-entry type and stays. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/guidance.ts`: rendered guidance (`/login anthropic-subscription`) and the override-prompt error use the new id/label. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts`: login label is `"Anthropic Subscription (Claude Pro/Max)"`; auth-check source follows. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts`: label constant and every notify/description string read "Anthropic Subscription". +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/affinity.ts`, `bounded-queue.ts`, `failover.ts`, `session-stream.ts`, `session-registry.ts`, `session-registry-pump.ts`, `session-reattach.ts`, `session-turn-claim.ts`: user-visible error/notify strings use the new label. `DEFAULT_AFFINITY_KEY` (`claude-sdk-oauth-default`) is FROZEN — it is the HRW hash-domain constant; renaming it silently remaps every unpinned session to a different account. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-observability.ts`: the close-cause regex accepts BOTH phrasings (`Claude SDK OAuth query|Anthropic Subscription query`) so classification survives the renamed pump messages. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/auth-lane.ts`: the no-managed-accounts error names the new lane and `/login anthropic-subscription`; `CLAUDE_CODE_OAUTH_TOKEN` unchanged. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/errors.ts`: doc comment names the new label. - Frozen and untouched in this directory: `api-id.ts` wire id, `accounts.ts` `claude-sdk-oauth-managed` literals, `session-binding.ts` `claude-sdk-oauth-binding`, `session-binding-store.ts` `.claude-sdk-oauth-binding.json`, `config-dir-credentials.ts` `claude-sdk-oauth-accounts` directory, every `claude_sdk_oauth_*` diagnostic token, module paths and symbols. ### Why @@ -106,8 +127,8 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/api-id.ts`: new `CLAUDE_SDK_OAUTH_API_ID`, frozen at `"claude-sdk-oauth"`. That string is the wire identity sent to the provider and persisted in user data; it must not follow a provider-id rename. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts`: `registerProvider` still takes the provider id from `CLAUDE_SDK_OAUTH_PROVIDER_ID`. `api` and `baseUrl` now come from `CLAUDE_SDK_OAUTH_API_ID`. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/api-id.ts`: new `CLAUDE_SDK_OAUTH_API_ID`, frozen at `"claude-sdk-oauth"`. That string is the wire identity sent to the provider and persisted in user data; it must not follow a provider-id rename. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts`: `registerProvider` still takes the provider id from `ANTHROPIC_SUBSCRIPTION_PROVIDER_ID`. `api` and `baseUrl` now come from `CLAUDE_SDK_OAUTH_API_ID`. - Guarded by `packages/coding-agent/test/suite/anthropic-subscription-api-id.test.ts`. A failure there means a rename went too far; restore the wire id, do not update the expected value. ### Why @@ -123,9 +144,9 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts`: `bindingFromStoredBranch` now admits an append-only tail after the committed assistant through a new `isAppendOnlyTailEntry` predicate - the existing ledger-only set, plus `custom_message` entries and `message` entries whose role is `user` or `toolResult`. A later assistant message, and every check around this one, still fail closed. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts`: `verifyRestoredTranscript` now also requires that NO top-level `user` frame follows the anchored assistant in the SDK transcript. Admitting the append-only tail alone would have been unsafe: a process that dies between pushing a turn and committing its assistant leaves that user frame in Claude Code's transcript while the ledger still anchors the previous assistant, so a plain resume from the anchor would send it a SECOND time. The in-memory retry checkpoint that forks past such an orphan (`unansweredTurnDigest`, senpi#723) is never persisted, so the restart path has no equivalent and fails closed instead. Only the orphan COUNT is logged (`claude_sdk_oauth_restored_transcript_orphan_tail`), never content; `session-observability.ts` gained `logContinuityEvent` so this module writes through the same boundary tests already override. Forking at the anchor instead of cold-seeding is the deferred half and belongs to senpi#1973. -- `packages/coding-agent/test/claude-sdk-oauth-binding-anchor.test.ts`: two assertions were REALIGNED, not deleted. "rejects a later user message after the committed assistant" and "rejects a model-visible custom message after the committed assistant" now expect a binding, with the reason recorded inline; new cases cover a later tool result, the terminal startup notice that triggered the report, and a later assistant message that must still reject. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding.ts`: `bindingFromStoredBranch` now admits an append-only tail after the committed assistant through a new `isAppendOnlyTailEntry` predicate - the existing ledger-only set, plus `custom_message` entries and `message` entries whose role is `user` or `toolResult`. A later assistant message, and every check around this one, still fail closed. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reattach.ts`: `verifyRestoredTranscript` now also requires that NO top-level `user` frame follows the anchored assistant in the SDK transcript. Admitting the append-only tail alone would have been unsafe: a process that dies between pushing a turn and committing its assistant leaves that user frame in Claude Code's transcript while the ledger still anchors the previous assistant, so a plain resume from the anchor would send it a SECOND time. The in-memory retry checkpoint that forks past such an orphan (`unansweredTurnDigest`, senpi#723) is never persisted, so the restart path has no equivalent and fails closed instead. Only the orphan COUNT is logged (`claude_sdk_oauth_restored_transcript_orphan_tail`), never content; `session-observability.ts` gained `logContinuityEvent` so this module writes through the same boundary tests already override. Forking at the anchor instead of cold-seeding is the deferred half and belongs to senpi#1973. +- `packages/coding-agent/test/anthropic-subscription-binding-anchor.test.ts`: two assertions were REALIGNED, not deleted. "rejects a later user message after the committed assistant" and "rejects a model-visible custom message after the committed assistant" now expect a binding, with the reason recorded inline; new cases cover a later tool result, the terminal startup notice that triggered the report, and a later assistant message that must still reject. ### Why @@ -138,14 +159,14 @@ This IS the extension; the provider id is its own registration constant and the - LOW: the predicate block near `isLedgerOnlyEntry` and the single guard line in `bindingFromStoredBranch`. - LOW: the anchor-index block in `verifyRestoredTranscript` and the new `logContinuityEvent` export in `session-observability.ts`. -- MEDIUM: `test/suite/regressions/7925-claude-sdk-oauth-restart-binding-ledger-entries.test.ts`, whose model-visible table was split into an append-only table and a retiring table. -- MEDIUM: `claude-sdk-oauth-binding-anchor.test.ts`, whose two flipped assertions will conflict with any upstream edit to the same cases. +- MEDIUM: `test/suite/regressions/7925-anthropic-subscription-restart-binding-ledger-entries.test.ts`, whose model-visible table was split into an append-only table and a retiring table. +- MEDIUM: `anthropic-subscription-binding-anchor.test.ts`, whose two flipped assertions will conflict with any upstream edit to the same cases. ## 2026-09-22 - A rejected fork point is dropped, not republished (senpi#1958) ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.ts`: recognizes Claude Code's `No message found with message.uuid of: ` alongside the existing missing-session wording, and removes exactly that id from `assistantUuidByIndex` before the retry checkpoint is published. Earlier mapped boundaries survive in the published checkpoint. This entry does NOT make the next admission fork at one of them: with `lastAssistantUuid` gone, `retryCheckpointDecision` and the legacy `decideFromBinding` branch in `session-continuity.ts` still answer `flatten` (`timeout_retry` / `registry_miss`) instead of consulting the surviving entries, so the turn after a rejected fork point re-sends once. Consuming those entries is senpi#1973; this entry only guarantees the dead id is never requested again. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-attempt.ts`: recognizes Claude Code's `No message found with message.uuid of: ` alongside the existing missing-session wording, and removes exactly that id from `assistantUuidByIndex` before the retry checkpoint is published. Earlier mapped boundaries survive in the published checkpoint. This entry does NOT make the next admission fork at one of them: with `lastAssistantUuid` gone, `retryCheckpointDecision` and the legacy `decideFromBinding` branch in `session-continuity.ts` still answer `flatten` (`timeout_retry` / `registry_miss`) instead of consulting the surviving entries, so the turn after a rejected fork point re-sends once. Consuming those entries is senpi#1973; this entry only guarantees the dead id is never requested again. ### Why @@ -185,11 +206,11 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts`: the `model_select` handler no longer invalidates when the newly selected model belongs to another provider. It now uses the existing `keepBindingThenClose`, exactly like `thinking_level_select` and the in-provider `switchSessionModel` failure branch, so the live SDK session closes while the binding and its sidecar survive. The same file now also carries the ledger invalidation cause into process memory: `persistBindingInvalidation` records it as it appends the record, `session_start` re-reads it from the branch on a restart (clearing it for `new`), and the `message_end` marker retires it. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts`: adds `invalidationReasonFromBranch`, which returns the reason of the newest binding ledger record when that record is an invalidation (a later marker retires it). -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts`: holds the pending invalidation reason per senpi session id (`rememberBindingInvalidation`, `bindingInvalidationReason`) next to the binding map it replaces. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts`: `ContinuityDecisionInput` accepts `invalidationReason`, and a `bootstrap`/`flatten` that would report the no-record default `registry_miss` reports the recorded cause instead (`model_selected`, `extensions_removed`, `assistant_rewritten` pass through the observation vocabulary; `compaction`, `tree_changed` and `fork` map to `tainted_compaction`, `branch_diverged` and `tainted_fork`). Classification is unchanged: no decision kind moves, and every other reason is left as decided. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-stream.ts`: passes the pending reason into the decision input. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-wiring.ts`: the `model_select` handler no longer invalidates when the newly selected model belongs to another provider. It now uses the existing `keepBindingThenClose`, exactly like `thinking_level_select` and the in-provider `switchSessionModel` failure branch, so the live SDK session closes while the binding and its sidecar survive. The same file now also carries the ledger invalidation cause into process memory: `persistBindingInvalidation` records it as it appends the record, `session_start` re-reads it from the branch on a restart (clearing it for `new`), and the `message_end` marker retires it. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding.ts`: adds `invalidationReasonFromBranch`, which returns the reason of the newest binding ledger record when that record is an invalidation (a later marker retires it). +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reattach.ts`: holds the pending invalidation reason per senpi session id (`rememberBindingInvalidation`, `bindingInvalidationReason`) next to the binding map it replaces. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-continuity.ts`: `ContinuityDecisionInput` accepts `invalidationReason`, and a `bootstrap`/`flatten` that would report the no-record default `registry_miss` reports the recorded cause instead (`model_selected`, `extensions_removed`, `assistant_rewritten` pass through the observation vocabulary; `compaction`, `tree_changed` and `fork` map to `tainted_compaction`, `branch_diverged` and `tainted_fork`). Classification is unchanged: no decision kind moves, and every other reason is left as decided. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-stream.ts`: passes the pending reason into the decision input. ### Why @@ -228,13 +249,13 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts`: `AccountSlot` carries optional `displayName`; immutable `name` remains the operational identity. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts`: explicitly projects safe display metadata alongside existing secret-free account descriptors. -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts`: adds rename/clear-name and renders display labels for listings, the pinned-account line and the affinity pick. Post-login naming is delegated to the shared helper, which offers it only for a machine-generated slot ID, so the lane's own "Name for this account" prompt is never followed by a second name prompt. Existing slot creation names, sentinel envelope, refresh, failover, affinity and session continuity are unchanged. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts`: `AccountSlot` carries optional `displayName`; immutable `name` remains the operational identity. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts`: explicitly projects safe display metadata alongside existing secret-free account descriptors. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts`: adds rename/clear-name and renders display labels for listings, the pinned-account line and the affinity pick. Post-login naming is delegated to the shared helper, which offers it only for a machine-generated slot ID, so the lane's own "Name for this account" prompt is never followed by a second name prompt. Existing slot creation names, sentinel envelope, refresh, failover, affinity and session continuity are unchanged. ### Why -- `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/accounts.ts`, `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-management.ts` and `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/account-command.ts`: readable labels must not change the account IDs responsible for Claude credentials, config roots and SDK session bindings. +- `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/accounts.ts`, `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-management.ts` and `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/account-command.ts`: readable labels must not change the account IDs responsible for Claude credentials, config roots and SDK session bindings. ### Why an extension could not handle it @@ -249,7 +270,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed - `accounts.ts`: `isSentinelSlot` recognizes a stored account whose `access` and `refresh` are both the managed sentinel, and `listAccounts` filters those out. -- `test/claude-sdk-oauth-accounts.test.ts`: a pool holding a real account plus a generated `login-2` sentinel slot lists only the real one. +- `test/anthropic-subscription-accounts.test.ts`: a pool holding a real account plus a generated `login-2` sentinel slot lists only the real one. ### Why @@ -287,7 +308,7 @@ This IS the extension; the provider id is its own registration constant and the - `session-reattach.ts`: the abort listener that bounds `initializationResult` is now removed in a `finally` block after initialization settles. -- `test/claude-sdk-oauth-reattach.test.ts`: cover both successful initialization +- `test/anthropic-subscription-reattach.test.ts`: cover both successful initialization followed by normal request cleanup and genuine abort during pending initialization. @@ -324,14 +345,14 @@ This IS the extension; the provider id is its own registration constant and the ### Expected merge conflict zones -- LOW: `executable.ts` is fork-only; `availability.ts` grows an export above `createAmbientAuthStatusReader`. `executable-path-lookup.ts` is new. Tests: `test/claude-sdk-oauth-executable*.test.ts`, `test/claude-sdk-oauth-availability.test.ts`. +- LOW: `executable.ts` is fork-only; `availability.ts` grows an export above `createAmbientAuthStatusReader`. `executable-path-lookup.ts` is new. Tests: `test/claude-sdk-oauth-executable*.test.ts`, `test/anthropic-subscription-availability.test.ts`. ## 2026-09-09 - Classify multi-message cold starts as bootstrap ### What changed - `session-stream.ts`: `createResidentAttempt` now identifies a first turn by the absence of any prior assistant message in `input.context.messages`, while retaining the no-resident-entry and no-persisted-binding checks. A fresh context can therefore bootstrap even when injected context and the actual prompt produce multiple transmitted user messages; a cold start after an assistant turn remains `flatten` with `registry_miss`. -- `claude-sdk-oauth-bootstrap-classification.test.ts` covers both observations through the resident session-stream fake SDK boundary. +- `anthropic-subscription-bootstrap-classification.test.ts` covers both observations through the resident session-stream fake SDK boundary. ### Why @@ -408,7 +429,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed - `session-continuity.ts`: `ContinuityDecisionInput.crossAccountResumeSupported` is a required boolean, filled by `session-stream.ts` from `auth.authLane !== "config-dir"`. `decideFromBinding` flattens account drift only with `cross_root_unsupported` on the config-dir lane; otherwise it falls through to the retry checkpoint (same-turn failover forks at the pre-turn boundary with reason `timeout_retry`) and the prefix checks (a matching prefix reattaches with reason `account_changed`). `model_changed` still flattens. -- Removed `decideFailoverContinuity`, `FailoverContinuityInput`, `FailoverLane`, and `test/claude-sdk-oauth-failover-continuity.test.ts`. Tests were flipped in `claude-sdk-oauth-restart-binding-drift.test.ts`, `claude-sdk-oauth-restored-security.test.ts`, and `claude-sdk-oauth-continuity-retry-checkpoint.test.ts`; added regression coverage in `suite/regressions/1432-claude-sdk-oauth-failover-reattach.test.ts`. +- Removed `decideFailoverContinuity`, `FailoverContinuityInput`, `FailoverLane`, and `test/claude-sdk-oauth-failover-continuity.test.ts`. Tests were flipped in `anthropic-subscription-restart-binding-drift.test.ts`, `anthropic-subscription-restored-security.test.ts`, and `anthropic-subscription-continuity-retry-checkpoint.test.ts`; added regression coverage in `suite/regressions/1432-anthropic-subscription-failover-reattach.test.ts`. ### Why @@ -429,7 +450,7 @@ This IS the extension; the provider id is its own registration constant and the - `session-continuity.ts`: `identityDrift` now reports `system_prompt_changed` / `toolset_changed` instead of the bare `options_changed`. `decideFromBinding` flattens only on `account_changed` / `model_changed`; prompt/toolset drift falls through to the normal prefix checks and a matching prefix reattaches with the drift reason (divergence reasons still win). The live-entry path is unchanged structurally and simply surfaces the split reasons. - `session-sync.ts`: `GENERATED_DATE_LINE` no longer anchors the `Current working directory:` line to end-of-string, so the date/cwd pair is neutralized wherever it sits in the prompt. -- Tests: `claude-sdk-oauth-restart-binding-drift.test.ts` (new) pins the restart contract and midnight stability with trailing appends; `claude-sdk-oauth-fingerprint.test.ts`, `claude-sdk-oauth-restored-security.test.ts`, `claude-sdk-oauth-continuity-decision.test.ts`, `claude-sdk-oauth-session-registry-wiring.test.ts` expectations moved from `options_changed` / `flatten` to the split reasons / `reattach`. +- Tests: `anthropic-subscription-restart-binding-drift.test.ts` (new) pins the restart contract and midnight stability with trailing appends; `anthropic-subscription-fingerprint.test.ts`, `anthropic-subscription-restored-security.test.ts`, `anthropic-subscription-continuity-decision.test.ts`, `anthropic-subscription-session-registry-wiring.test.ts` expectations moved from `options_changed` / `flatten` to the split reasons / `reattach`. ### Why @@ -451,7 +472,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed - `session-registry-pump.ts` and `session-turn-claim.ts` retain a matching SDK result received before replay claim and attribute it when the claim arrives instead of discarding the turn as `SessionTurnAttributionError`. -- `test/claude-sdk-oauth-session-registry.test.ts` covers the deterministic early-result ordering. +- `test/anthropic-subscription-session-registry.test.ts` covers the deterministic early-result ordering. ### Why @@ -510,7 +531,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `tools.ts`: `TOOL_EXECUTION_DENIED_MESSAGE` and `HOST_TOOL_EXECUTION_DENIED_MESSAGE` now state that Senpi executes the tool on the host and returns the result as the next user message; the copy no longer tells the model to end the turn. `HOST_TOOL_POLICY_FINGERPRINT` is `host-tool-denial-v2` so resident sessions re-fingerprint instead of silently keeping the old denial text; because `toolsetHash` is also persisted in the restart sidecar, the first admission after upgrading cold-seeds once (`flatten` / `options_changed`) instead of resuming the pre-bump SDK session - intended, pinned by `claude-sdk-oauth-fingerprint.test.ts`. PreToolUse still denies with `continue: false` and `permissionDecisionReason`; there is no Stop hook. +- `tools.ts`: `TOOL_EXECUTION_DENIED_MESSAGE` and `HOST_TOOL_EXECUTION_DENIED_MESSAGE` now state that Senpi executes the tool on the host and returns the result as the next user message; the copy no longer tells the model to end the turn. `HOST_TOOL_POLICY_FINGERPRINT` is `host-tool-denial-v2` so resident sessions re-fingerprint instead of silently keeping the old denial text; because `toolsetHash` is also persisted in the restart sidecar, the first admission after upgrading cold-seeds once (`flatten` / `options_changed`) instead of resuming the pre-bump SDK session - intended, pinned by `anthropic-subscription-fingerprint.test.ts`. PreToolUse still denies with `continue: false` and `permissionDecisionReason`; there is no Stop hook. - `session-sync.ts`: `configFingerprint` still hashes `hostToolPolicy: HOST_TOOL_POLICY_FINGERPRINT` into `toolsetHash`; the bump is what makes a wording-only denial change retire live queries via `options_changed`. ### Why @@ -531,7 +552,7 @@ This IS the extension; the provider id is its own registration constant and the ### Why -- After `/logout claude-sdk-oauth` (or with a hand-written `tokenInjection: "oauth-slots"` and no slots), the combined `lane === "ambient" || accounts.length === 0` guard returned `undefined`, and `queryWithAuthLane` then built an ambient child environment and ran the SDK against whatever the host `claude` CLI happened to be logged into. A user who chose a managed lane silently got answers billed to an unrelated local Claude login, and `/logout` was not authoritative. The availability predicate in `oauth-login.ts` can hide the provider, but `streamClaudeSdkOauth` reaches `queryWithAuthLane` directly, so the spawn path had to refuse on its own. +- After `/logout claude-sdk-oauth` (or with a hand-written `tokenInjection: "oauth-slots"` and no slots), the combined `lane === "ambient" || accounts.length === 0` guard returned `undefined`, and `queryWithAuthLane` then built an ambient child environment and ran the SDK against whatever the host `claude` CLI happened to be logged into. A user who chose a managed lane silently got answers billed to an unrelated local Claude login, and `/logout` was not authoritative. The availability predicate in `oauth-login.ts` can hide the provider, but `streamAnthropicSubscription` reaches `queryWithAuthLane` directly, so the spawn path had to refuse on its own. - The ambient default of oh-my-openagent#6784 is preserved deliberately: unset `tokenInjection` with an empty store still resolves to `ambient` through `resolveEffectiveLane` and keeps spawning ambiently. ### Why an extension could not handle it @@ -545,7 +566,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `session-registry.ts`: `ClaudeSdkOauthSessionEntry.sdkSessionIdConfirmed` records whether the SDK acknowledged the entry's session id; entries created from a resume start confirmed, entries whose id `getOrCreate` minted locally start unconfirmed. +- `session-registry.ts`: `AnthropicSubscriptionSessionEntry.sdkSessionIdConfirmed` records whether the SDK acknowledged the entry's session id; entries created from a resume start confirmed, entries whose id `getOrCreate` minted locally start unconfirmed. - `session-registry-pump.ts`: a `system`/`init` message and the replay echo that claims the turn both mark the entry confirmed (either proves Claude Code runs under that id). - `session-reattach.ts`: `ContinuityBinding.sdkSessionIdConfirmed` carries the flag; `bindingFromEntry` copies it. - `session-turn-attempt.ts`: bindings are published with the flag instead of silently; an attempt whose failure says `No conversation found with session ID` forgets the binding outright, because Claude Code has declared the bound id dead. @@ -638,7 +659,7 @@ This IS the extension; the provider id is its own registration constant and the ### Expected merge conflict zones -- LOW in `options.ts` around `buildClaudeSdkOauthQueryOptions` tool selection and strict MCP configuration. +- LOW in `options.ts` around `buildAnthropicSubscriptionQueryOptions` tool selection and strict MCP configuration. - LOW in `stream.ts` around non-resident custom MCP server construction. @@ -664,7 +685,7 @@ This IS the extension; the provider id is its own registration constant and the ### What changed -- `settings.ts`: `loadClaudeSdkOauthProviderSettingsFromDisk` caches the `SettingsManager` instance keyed on (cwd, mtimeMs:size of the global and project settings.json). A cache hit skips `SettingsManager.create` and its two locked disk reads; environment overrides are re-parsed on every call so live env changes take effect immediately. +- `settings.ts`: `loadAnthropicSubscriptionProviderSettingsFromDisk` caches the `SettingsManager` instance keyed on (cwd, mtimeMs:size of the global and project settings.json). A cache hit skips `SettingsManager.create` and its two locked disk reads; environment overrides are re-parsed on every call so live env changes take effect immediately. ### Why @@ -676,7 +697,7 @@ This IS the extension; the provider id is its own registration constant and the ### Expected merge conflict zones -- `settings.ts` around `loadClaudeSdkOauthProviderSettingsFromDisk`. +- `settings.ts` around `loadAnthropicSubscriptionProviderSettingsFromDisk`. ## 2026-08-20 - Terminate policy refusals without waiting for the stream watchdog @@ -689,7 +710,7 @@ This IS the extension; the provider id is its own registration constant and the - `auth-lane.ts` classifies that error as non-retryable before account failover, `stream.ts` terminates non-resident streams immediately, and `session-registry-pump.ts` settles and closes a resident turn without reading another SDK message. -- `test/claude-sdk-oauth-refusal.test.ts` covers structured ambient and resident refusals plus the legacy assistant +- `test/anthropic-subscription-refusal.test.ts` covers structured ambient and resident refusals plus the legacy assistant frame, with iterators that fail if consumption proceeds past the refusal. ### Why @@ -718,7 +739,7 @@ been consumed here. rides the existing clone/remember paths but is NEVER persisted: `storedBindingFromEntry` (`session-binding.ts`) builds the sidecar record from an explicit field list, and the strict `schemaVersion: 1` schema (`session-binding-store.ts`) rejects any record carrying it. - Tests: `test/claude-sdk-oauth-binding-store.test.ts` (round-trip rejects it). + Tests: `test/anthropic-subscription-binding-store.test.ts` (round-trip rejects it). - `session-turn-attempt.ts`: an attempt that pushed its user payload but ended aborted, failed, or discarded now remembers a retry checkpoint binding anchored at the PRE-TURN boundary (`bindingFromEntry(entry, hashes.slice(0, entry.sentCount))` + the attempted turn's full sent-stream @@ -731,8 +752,8 @@ been consumed here. turn). A digest mismatch falls through to the pre-existing branches unchanged. - `session-observability.ts`: `ContinuityReason` union and the sanitizer allowlist admit `timeout_retry`. No new event types; one observation per main turn is preserved. -- Tests: `test/suite/regressions/723-claude-sdk-oauth-timeout-abort-retry-continuity.test.ts`, - `test/claude-sdk-oauth-continuity-decision.test.ts`, `test/claude-sdk-oauth-continuity-retry-checkpoint.test.ts`. +- Tests: `test/suite/regressions/723-anthropic-subscription-timeout-abort-retry-continuity.test.ts`, + `test/anthropic-subscription-continuity-decision.test.ts`, `test/anthropic-subscription-continuity-retry-checkpoint.test.ts`. ### Why @@ -773,7 +794,7 @@ A stream-start-timeout abort closes the SDK session with the turn's user message and therefore require the preload). Its exported types are now derived from the loader's module type instead of from value imports. - The three async entry points that reach a synchronous SDK member now await the loader first: - `stream.ts` (`streamClaudeSdkOauth`, before `getSdkBoundary().query` and the resident-session lane), + `stream.ts` (`streamAnthropicSubscription`, before `getSdkBoundary().query` and the resident-session lane), `session-reattach.ts` (`reattachSession`, before `getOrCreateSession` builds a query), and `custom-tools.ts` (`buildCustomToolServers`, now async, before `createSdkMcpServer`). A future call site that skips the preload fails with a named error at that call rather than silently restoring the startup import. @@ -806,7 +827,7 @@ A stream-start-timeout abort closes the SDK session with the turn's user message - `index.ts`: the provider registration passes `fallbackEligible`, returning false only under the verbatim `enabled: false` kill switch. An absent flag and unreadable settings stay eligible, so an explicit senpi-side login keeps the lane in bare-family fallback expansion. - Tests: `test/suite/claude-sdk-oauth-fallback-eligibility.test.ts`. + Tests: `test/suite/anthropic-subscription-fallback-eligibility.test.ts`. ### Why @@ -855,11 +876,11 @@ A stream-start-timeout abort closes the SDK session with the turn's user message ### Expected merge-conflict zones -- LOW in `settings.ts` (`ClaudeSdkOauthProviderSettings` field list, `parseProviderSettings` and +- LOW in `settings.ts` (`AnthropicSubscriptionProviderSettings` field list, `parseProviderSettings` and `parseEnvironmentSettings` return literals - upstream additions land in the same two literals). - MEDIUM in `oauth-login.ts` at the `configuredFor` ambient branch, which upstream also touches for lane selection. - LOW in `index.ts` at the `createOAuthConfig` deps literal. -- LOW in `test/support/claude-sdk-oauth-provider.ts` (`composedProvider` gained a third `settings` parameter) and in the +- LOW in `test/support/anthropic-subscription-provider.ts` (`composedProvider` gained a third `settings` parameter) and in the ambient tests that now pass `{ enabled: true }`. ## 2026-08-18 - Anchor restart records at branch state (issue #6981 review) @@ -967,7 +988,7 @@ A stream-start-timeout abort closes the SDK session with the turn's user message - This entry is the canonical inventory for the repository-wide changes.md audit (`scripts/audit-changes-md.mjs`, pin `914cf1472e715297caa30db4b9535d534a9eb718`). The audited production paths whose exact nearest tracker is this file: - none — every file under `packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/` is fork-only (absent + none — every file under `packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/` is fork-only (absent from the pinned upstream tree), so the audit assigns this tracker no upstream-owned divergence. - Two leftover diff3 conflict-separator lines were removed from the historical entries below; the surrounding history is preserved unchanged. @@ -1071,7 +1092,7 @@ A stream-start-timeout abort closes the SDK session with the turn's user message window on Windows. - Merged with the bounded ambient probe: `windowsHide` moved onto the default `spawnProbe`, so the injected-spawn path and the real spawn stay identical. -- `claude-sdk-oauth-availability.test.ts` drives the two outcome cases through +- `anthropic-subscription-availability.test.ts` drives the two outcome cases through `probeAmbientClaudeAuthStatus` instead of the reader, because the reader memoises for 30s and would replay the first probe's `true` to both. @@ -1112,8 +1133,8 @@ A stream-start-timeout abort closes the SDK session with the turn's user message - `auth-lane.ts` `managedPool` no longer defaults `tokenInjection` to `ambient`. A new exported `resolveEffectiveLane(settings, accounts)` resolves the lane as `settings.tokenInjection ?? (accounts.length > 0 ? "oauth-slots" : "ambient")`. - `oauth-login.ts` `createOAuthConfig` gains an optional `readSettings` dep so the OAuth `check` resolves the same effective lane. On a managed lane it reports configured only when accounts exist; on ambient it defers to the ambient Claude CLI probe. -- `index.ts` wires `readSettings` to `loadClaudeSdkOauthProviderSettingsFromDisk(process.cwd())`. -- `options.ts` `buildClaudeSdkOauthQueryOptions` aligns its `authLane` fallback to `ambient` so both lane-resolution sites agree (the previous `?? "oauth-slots"` was a dead default contradicted by `managedPool`). +- `index.ts` wires `readSettings` to `loadAnthropicSubscriptionProviderSettingsFromDisk(process.cwd())`. +- `options.ts` `buildAnthropicSubscriptionQueryOptions` aligns its `authLane` fallback to `ambient` so both lane-resolution sites agree (the previous `?? "oauth-slots"` was a dead default contradicted by `managedPool`). ### Why @@ -1127,7 +1148,7 @@ The lane decision lives inside the builtin provider's own `queryWithAuthLane`/`m ### Expected merge-conflict zones -LOW in `auth-lane.ts` (new `resolveEffectiveLane` + `managedPool` lane resolution); LOW in `oauth-login.ts` (`readSettings` dep + lane-aware `check`); LOW in `index.ts` (one new import + `readSettings` wiring); LOW in `options.ts` (one fallback literal). LOW in `test/claude-sdk-oauth-auth-status.test.ts` and `test/suite/regressions/6784-claude-sdk-oauth-default-lane.test.ts`. +LOW in `auth-lane.ts` (new `resolveEffectiveLane` + `managedPool` lane resolution); LOW in `oauth-login.ts` (`readSettings` dep + lane-aware `check`); LOW in `index.ts` (one new import + `readSettings` wiring); LOW in `options.ts` (one fallback literal). LOW in `test/anthropic-subscription-auth-status.test.ts` and `test/suite/regressions/6784-anthropic-subscription-default-lane.test.ts`. ## 2026-08-13 - Bound the ambient probe and let an abandoned request stop waiting @@ -1234,7 +1255,7 @@ LOW in `oauth-login.ts` (added `check` to the returned shape + optional `readSet stay hash-significant, so genuine rewrites of already-sent history still resolve to `sent_stream_diverged`. - This cannot be implemented by an external extension: the transmitted set is computed inside the builtin provider before session-registry continuity is decided, and no extension hook can replace it. -- Added `test/suite/regressions/790-claude-sdk-oauth-empty-user-continuity.test.ts` covering the transient empty +- Added `test/suite/regressions/790-anthropic-subscription-empty-user-continuity.test.ts` covering the transient empty message, a plain append, a genuine rewrite, and the filter itself. - Expected merge conflict zones: LOW in `session-sync.ts` (`sentMessages`); LOW in the new regression test. @@ -1249,7 +1270,7 @@ LOW in `oauth-login.ts` (added `check` to the returned shape + optional `readSet continuity divergence handling. - Added a deterministic issue #691 regression covering both the timing-only and semantic-change cases. - Expected merge conflict zones: LOW in `session-commit-boundary.ts`; LOW in - `test/suite/regressions/691-claude-sdk-oauth-thinking-timing.test.ts`. + `test/suite/regressions/691-anthropic-subscription-thinking-timing.test.ts`. ## 2026-08-04 - Surface flatten payload size and collapsed directive count in continuity observations @@ -1308,7 +1329,7 @@ LOW in `oauth-login.ts` (added `check` to the returned shape + optional `readSet - **Environment overrides.** Six variables, precedence `env > project settings > global settings > default`. No new CLI flags: `SENPI_CLAUDE_SDK_OAUTH_SYSTEM_PROMPT_MODE`, `SENPI_CLAUDE_SDK_OAUTH_SYSTEM_PROMPT_FILE`, `SENPI_CLAUDE_SDK_OAUTH_RESUME`, `SENPI_CLAUDE_SDK_OAUTH_TOKEN_INJECTION`, `SENPI_CLAUDE_SDK_OAUTH_SETTING_SOURCES`, `SENPI_CLAUDE_SDK_OAUTH_PINNED_ACCOUNT`. Every `SENPI_*` variable is now stripped from the Claude Code subprocess environment on all three lanes (oauth-slots, config-dir, ambient); other inherited variables are preserved. - **Session reuse.** One long-lived SDK query per senpi session instead of a fresh one per turn, so a conversation continues instead of cold-starting each turn and only the new delta is sent. Always fails closed to a fresh session when the conversation diverges: compaction, branch/fork navigation, account failover, an aborted turn, or a configuration change. Idle sessions are retired after 30 minutes and at most 32 sessions stay resident; a session with a turn in flight is never evicted. After a senpi process restart the lane always starts a fresh SDK session rather than trying to re-attach. `resumeMode` accepts `"auto"` (default) and `"off"`; set `resumeMode: "off"` (or `SENPI_CLAUDE_SDK_OAUTH_RESUME=off`) to restore the old per-turn behaviour. Any other value is silently ignored (falls back to `"auto"`). - **Fallback transcript hardening.** When a full re-send is unavoidable, the flattened history is wrapped in a `` envelope with an explicit anchor instruction and the real user message placed last and unlabelled. Previously the flat `USER:`/`ASSISTANT:` transcript read as a continuable document and baited the model into fabricating its own turns. -- Merge-conflict risk: low. The only overlap surface is the settings/env-resolution block in `buildClaudeSdkOauthQueryOptions`, which the concurrent `stream.ts` / `auth-lane.ts` work also touches. +- Merge-conflict risk: low. The only overlap surface is the settings/env-resolution block in `buildAnthropicSubscriptionQueryOptions`, which the concurrent `stream.ts` / `auth-lane.ts` work also touches. ## 2026-07-31 - Rename the internal provider identity @@ -1325,9 +1346,9 @@ LOW in `oauth-login.ts` (added `check` to the returned shape + optional `readSet - The region is located by the rules builtin's opaque region sentinels, not by the model-facing `` tags: prompt content this lane does not own (context files before the block, extensions appending after it) may legitimately contain those tags and would otherwise be extracted as project rules. Rule content quoting either the sentinels or the tags is neutralized producer-side. - The sentinels are a reserved wire literal, but nothing neutralizes them in content the rules builtin does not produce, so every sentinel candidate is structurally validated (it must open with the `` tag followed by the `## Project Instructions` heading and close with the tag) and rejected candidates are skipped. Without that, an `AGENTS.md` carrying a sentinel would either shadow the real block or cross-match its end sentinel and hand the model unrelated text as project rules. Replicating a complete, well-formed frame is out of scope — that is a trusted-extension boundary, not a parsing one. - Extraction is fail-closed: a region missing its end sentinel is skipped rather than read to end-of-string, so sections appended by extensions registered after `rules` (`mcp`) are never relabelled as project rules. The forwarded append carries the `` envelope; the sentinels themselves are stripped. -- Why an extension could not handle it: the `append` list is assembled inside `buildClaudeSdkOauthQueryOptions`, which no extension hook can reach. +- Why an extension could not handle it: the `append` list is assembled inside `buildAnthropicSubscriptionQueryOptions`, which no extension hook can reach. - Scope note: the other `before_agent_start` system-prompt mutations dropped by this lane (`hooks`, `compaction`, `mcp`, `terminal`, `todotools`, web search) and the project `CLAUDE.md` / parent context files are unchanged here and remain open. -- Merge-conflict risk: low. Expected conflict zones are the `append` array literal in `buildClaudeSdkOauthQueryOptions` and the extractor cluster next to `extractSkillsAppend`. +- Merge-conflict risk: low. Expected conflict zones are the `append` array literal in `buildAnthropicSubscriptionQueryOptions` and the extractor cluster next to `extractSkillsAppend`. ## 2026-07-30 - Terminal pre-execution denial for host-captured tools (#494) diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/config-dir-credentials.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/config-dir-credentials.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/config-dir-credentials.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/config-dir-credentials.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/content-blocks.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/content-blocks.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/content-blocks.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/content-blocks.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/custom-tools-schema.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/custom-tools-schema.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/custom-tools-schema.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/custom-tools-schema.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/custom-tools.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/custom-tools.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/custom-tools.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/custom-tools.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/errors.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/errors.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/errors.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/errors.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable-path-lookup.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable-path-lookup.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable-path-lookup.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable-path-lookup.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/executable.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/executable.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/failover.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/failover.ts similarity index 97% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/failover.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/failover.ts index d27211e1e0..e73efb84a5 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/failover.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/failover.ts @@ -1,5 +1,5 @@ import type { CredentialStore } from "@earendil-works/pi-ai"; -import type { AccountSlot, ClaudeSdkOauthCredential } from "./accounts.ts"; +import type { AccountSlot, AnthropicSubscriptionCredential } from "./accounts.ts"; import { clearExpiredBlocks } from "./affinity.ts"; import type { SdkErrorClassification } from "./errors.ts"; @@ -103,7 +103,7 @@ function replaceAccount(accounts: readonly AccountSlot[], replacement: AccountSl async function persistBlock(store: CredentialStore, providerId: string, account: AccountSlot): Promise { await store.modify(providerId, async (current) => { if (current?.type !== "oauth") return current; - const credential = current as ClaudeSdkOauthCredential; + const credential = current as AnthropicSubscriptionCredential; if (account.source === "env") { return { ...credential, diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/guidance.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/guidance.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/guidance.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/guidance.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts similarity index 73% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts index 4f075bc42b..34b4b5cbae 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/index.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/index.ts @@ -6,19 +6,22 @@ import { getAgentDir } from "../../../../config.ts"; import { FileAuthStorageBackend } from "../../../auth-storage.ts"; import type { ExtensionAPI } from "../../types.ts"; import { registerClaudeAccountCommand } from "./account-command.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; -import type { ClaudeSdkOauthCredential } from "./accounts.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; +import type { AnthropicSubscriptionCredential } from "./accounts.ts"; import { CLAUDE_SDK_OAUTH_API_ID } from "./api-id.ts"; import { createOAuthConfig } from "./oauth-login.ts"; import { registerSessionRegistry } from "./session-registry-wiring.ts"; -import { type ClaudeSdkOauthProviderSettings, loadClaudeSdkOauthProviderSettingsFromDisk } from "./settings.ts"; -import { streamClaudeSdkOauth } from "./stream.ts"; +import { + type AnthropicSubscriptionProviderSettings, + loadAnthropicSubscriptionProviderSettingsFromDisk, +} from "./settings.ts"; +import { streamAnthropicSubscription } from "./stream.ts"; -export { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; -export type ClaudeSdkOauthExtensionDeps = { +export { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; +export type AnthropicSubscriptionExtensionDeps = { readAmbientAuthStatus?: () => Promise; /** Overrides the on-disk provider settings the auth predicate consults. */ - readSettings?: () => ClaudeSdkOauthProviderSettings; + readSettings?: () => AnthropicSubscriptionProviderSettings; }; const MODELS = getModels("anthropic").map((model) => ({ @@ -35,11 +38,11 @@ const MODELS = getModels("anthropic").map((model) => ({ }, })); -function readStoredCredential(providerId: string): ClaudeSdkOauthCredential | undefined { +function readStoredCredential(providerId: string): AnthropicSubscriptionCredential | undefined { const authPath = join(getAgentDir(), "auth.json"); if (!existsSync(authPath)) return undefined; try { - const data = JSON.parse(readFileSync(authPath, "utf8")) as Record; + const data = JSON.parse(readFileSync(authPath, "utf8")) as Record; // Read boundary (senpi#1989): this raw read bypasses AuthStorage, so it // never sees the auth.json key migration. An auth.json still keyed by the // legacy provider id would report the lane logged out with no error at @@ -51,14 +54,17 @@ function readStoredCredential(providerId: string): ClaudeSdkOauthCredential | un } } -export function registerClaudeSdkOauthExtension(pi: ExtensionAPI, deps: ClaudeSdkOauthExtensionDeps = {}): void { +export function registerAnthropicSubscriptionExtension( + pi: ExtensionAPI, + deps: AnthropicSubscriptionExtensionDeps = {}, +): void { registerClaudeAccountCommand(pi); registerSessionRegistry(pi); - pi.registerProvider(CLAUDE_SDK_OAUTH_PROVIDER_ID, { + pi.registerProvider(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, { baseUrl: CLAUDE_SDK_OAUTH_API_ID, api: CLAUDE_SDK_OAUTH_API_ID, models: MODELS, - streamSimple: streamClaudeSdkOauth, + streamSimple: streamAnthropicSubscription, // A verbatim `enabled: false` is the kill switch: the lane cannot serve, so // it must not consume an implicit fallback-expansion slot. An absent flag // stays eligible - an explicit senpi-side login keeps the lane usable. @@ -66,20 +72,20 @@ export function registerClaudeSdkOauthExtension(pi: ExtensionAPI, deps: ClaudeSd try { const settings = deps.readSettings ? deps.readSettings() - : loadClaudeSdkOauthProviderSettingsFromDisk(process.cwd()); + : loadAnthropicSubscriptionProviderSettingsFromDisk(process.cwd()); return settings.enabled !== false; } catch { return true; } }, oauth: createOAuthConfig({ - readCurrent: async () => readStoredCredential(CLAUDE_SDK_OAUTH_PROVIDER_ID), + readCurrent: async () => readStoredCredential(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID), readAmbientAuthStatus: deps.readAmbientAuthStatus, readSettings: () => { try { const settings = deps.readSettings ? deps.readSettings() - : loadClaudeSdkOauthProviderSettingsFromDisk(process.cwd()); + : loadAnthropicSubscriptionProviderSettingsFromDisk(process.cwd()); return { tokenInjection: settings.tokenInjection, enabled: settings.enabled }; } catch { return undefined; @@ -107,6 +113,6 @@ export function registerClaudeSdkOauthExtension(pi: ExtensionAPI, deps: ClaudeSd }); } -export default function claudeSdkOauthExtension(pi: ExtensionAPI): void { - registerClaudeSdkOauthExtension(pi); +export default function anthropicSubscriptionExtension(pi: ExtensionAPI): void { + registerAnthropicSubscriptionExtension(pi); } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts similarity index 95% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts index 30a0c447a0..bebb17303f 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/oauth-login.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/oauth-login.ts @@ -10,8 +10,8 @@ import type { import { loadAnthropicOAuth } from "@earendil-works/pi-ai/oauth"; import { type AccountSlot, + type AnthropicSubscriptionCredential, addAccount, - type ClaudeSdkOauthCredential, emptyCredential, listAccounts, SENTINEL_OAUTH_FIELDS, @@ -27,7 +27,7 @@ export type OAuthLoginCallbacks = { onProgress?: (message: string) => void; }; -export type CurrentCredentialReader = () => Promise; +export type CurrentCredentialReader = () => Promise; export type OAuthConfigShape = { name: string; @@ -53,7 +53,7 @@ export type OAuthConfigShape = { getApiKey(credentials: OAuthCredentials): string; }; -export const CLAUDE_SDK_OAUTH_NAME = "Anthropic Subscription (Claude Pro/Max)"; +export const ANTHROPIC_SUBSCRIPTION_NAME = "Anthropic Subscription (Claude Pro/Max)"; const ENV_TOKEN_NAMES = [ "CLAUDE_CODE_OAUTH_TOKEN", ...Array.from({ length: 15 }, (_, index) => `CLAUDE_CODE_OAUTH_TOKEN_${index + 2}`), @@ -128,7 +128,7 @@ export function createOAuthConfig(deps: { /** Single predicate behind both `check` and `resolveAmbient`, so availability and resolution cannot disagree. */ const configuredFor = async ( ctx: AuthContext, - stored: ClaudeSdkOauthCredential | undefined, + stored: AnthropicSubscriptionCredential | undefined, signal?: AbortSignal, environment?: Record, ): Promise => { @@ -156,13 +156,13 @@ export function createOAuthConfig(deps: { }; return { - name: CLAUDE_SDK_OAUTH_NAME, + name: ANTHROPIC_SUBSCRIPTION_NAME, async check({ ctx, credential, signal }) { const requestEnvironment = requestClaudeEnvironment(credential?.env); return (await configuredFor( ctx, - credential as ClaudeSdkOauthCredential | undefined, + credential as AnthropicSubscriptionCredential | undefined, signal, Object.keys(requestEnvironment).length > 0 ? requestEnvironment : undefined, )) diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/options.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/options.ts similarity index 92% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/options.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/options.ts index fceed3b155..c0118042da 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/options.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/options.ts @@ -12,24 +12,24 @@ import { import { presetAppendDeprecationGuidance } from "./guidance.ts"; import type { EffortLevel, Options, SettingSource, ThinkingConfig } from "./sdk-boundary.ts"; import { - type ClaudeSdkOauthProviderSettings, - type ClaudeSdkOauthSystemPromptMode, - type ClaudeSdkOauthTokenInjection, - loadClaudeSdkOauthProviderSettingsFromDisk, + type AnthropicSubscriptionProviderSettings, + type AnthropicSubscriptionSystemPromptMode, + type AnthropicSubscriptionTokenInjection, + loadAnthropicSubscriptionProviderSettingsFromDisk, resolveSystemPromptMode, } from "./settings.ts"; import { loadOverrideSystemPrompt, resolveCustomSystemPrompt } from "./system-prompt.ts"; import { BUILTIN_SDK_TOOLS, canUseTool, HOST_TOOL_DENIAL_HOOKS } from "./tools.ts"; -export type ClaudeSdkOauthAuthLane = ClaudeSdkOauthTokenInjection; +export type AnthropicSubscriptionAuthLane = AnthropicSubscriptionTokenInjection; -export interface ClaudeSdkOauthQueryOptionsInput { +export interface AnthropicSubscriptionQueryOptionsInput { readonly model: Model; readonly context: Context; readonly streamOptions?: SimpleStreamOptions; readonly cwd?: string; - readonly providerSettings?: ClaudeSdkOauthProviderSettings; - readonly authLane?: ClaudeSdkOauthAuthLane; + readonly providerSettings?: AnthropicSubscriptionProviderSettings; + readonly authLane?: AnthropicSubscriptionAuthLane; readonly tools?: readonly string[]; readonly pathToClaudeCodeExecutable?: string; readonly sessionId?: string; @@ -207,18 +207,18 @@ function isProjectRulesEnvelope(region: string): boolean { } function resolveSettingSources( - providerSettings: ClaudeSdkOauthProviderSettings, - mode: ClaudeSdkOauthSystemPromptMode, - authLane: ClaudeSdkOauthAuthLane, + providerSettings: AnthropicSubscriptionProviderSettings, + mode: AnthropicSubscriptionSystemPromptMode, + authLane: AnthropicSubscriptionAuthLane, ): SettingSource[] { if (providerSettings.settingSources !== undefined) return [...providerSettings.settingSources]; if (mode !== "preset-append" || authLane !== "ambient") return []; return ["user", "project"]; } -export function buildClaudeSdkOauthQueryOptions(input: ClaudeSdkOauthQueryOptionsInput): Options { +export function buildAnthropicSubscriptionQueryOptions(input: AnthropicSubscriptionQueryOptionsInput): Options { const cwd = input.cwd ?? process.cwd(); - const providerSettings = input.providerSettings ?? loadClaudeSdkOauthProviderSettingsFromDisk(cwd); + const providerSettings = input.providerSettings ?? loadAnthropicSubscriptionProviderSettingsFromDisk(cwd); const appendSystemPrompt = providerSettings.appendSystemPrompt !== false; const resolvedMode = resolveSystemPromptMode(providerSettings); const mode = resolvedMode.mode; diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/prompt-bridge.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/prompt-bridge.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/prompt-bridge.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/prompt-directive-dedupe.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/prompt-directive-dedupe.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/prompt-directive-dedupe.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/prompt-directive-dedupe.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/refusal.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/refusal.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/refusal.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/refusal.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.ts similarity index 96% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.ts index ec004678a5..834e8020fa 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.lazy.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/sdk-boundary.lazy.ts @@ -11,7 +11,7 @@ * That split is what keeps the deferral honest: the SDK's `query` and * `createSdkMcpServer` are synchronous, so their call sites cannot await. Every * one of them is reached from an async entry point that awaits this loader - * first (the `streamClaudeSdkOauth` async body, `reattachSession`, + * first (the `streamAnthropicSubscription` async body, `reattachSession`, * `verifyRestoredTranscript`), so the module is always resident by the time a * synchronous member is read — and a future call site that forgets to preload * fails loudly and locally instead of silently regressing startup. diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/sdk-boundary.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/sdk-boundary.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/sdk-boundary.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding-store.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding-store.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding-store.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding-store.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-binding.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-binding.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-commit-boundary.ts similarity index 95% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-commit-boundary.ts index 83d7cf37fd..6e964149d0 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-commit-boundary.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-commit-boundary.ts @@ -1,5 +1,5 @@ import type { AssistantMessage } from "@earendil-works/pi-ai"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; import { CLAUDE_SDK_OAUTH_API_ID } from "./api-id.ts"; import { sessionSyncDigest } from "./session-sync.ts"; @@ -41,7 +41,7 @@ export function assistantContentHash(message: AssistantMessage): string { export function isResidentAssistant(message: AssistantMessage, modelId: string): boolean { return ( message.api === CLAUDE_SDK_OAUTH_API_ID && - message.provider === CLAUDE_SDK_OAUTH_PROVIDER_ID && + message.provider === ANTHROPIC_SUBSCRIPTION_PROVIDER_ID && message.model === modelId ); } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-continuity.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-continuity.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-continuity.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-entry-annotations.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-entry-annotations.ts similarity index 84% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-entry-annotations.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-entry-annotations.ts index 0f01117806..6c204d1c71 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-entry-annotations.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-entry-annotations.ts @@ -1,9 +1,9 @@ -import type { ClaudeSdkOauthSessionEntry, SessionBranchInfo } from "./session-registry.ts"; +import type { AnthropicSubscriptionSessionEntry, SessionBranchInfo } from "./session-registry.ts"; import { transitionToTainted } from "./session-registry-state.ts"; export type AnnotatableRegistry = { - get(senpiSessionId: string): ClaudeSdkOauthSessionEntry | undefined; - touch(entry: ClaudeSdkOauthSessionEntry): void; + get(senpiSessionId: string): AnthropicSubscriptionSessionEntry | undefined; + touch(entry: AnthropicSubscriptionSessionEntry): void; }; export async function switchEntryModel( diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-observability.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-observability.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-observability.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-observability.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reaper.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reaper.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reaper.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reaper.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reattach.ts similarity index 94% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reattach.ts index 54da99dc9f..7779e1188b 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-reattach.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-reattach.ts @@ -1,8 +1,8 @@ -import type { ClaudeSdkOauthAuthLane } from "./options.ts"; +import type { AnthropicSubscriptionAuthLane } from "./options.ts"; import type { Options, SessionMessage } from "./sdk-boundary.ts"; import { getSdkBoundary, loadClaudeAgentSdk } from "./sdk-boundary.ts"; import { logContinuityEvent } from "./session-observability.ts"; -import { type ClaudeSdkOauthSessionEntry, closeSession, getOrCreateSession } from "./session-registry.ts"; +import { type AnthropicSubscriptionSessionEntry, closeSession, getOrCreateSession } from "./session-registry.ts"; import { recordSyncedStream } from "./session-sync.ts"; export type ContinuityBinding = { @@ -90,7 +90,7 @@ export function bindingInvalidationReason(senpiSessionId: string): string | unde export function bindingFromEntry( entry: Pick< - ClaudeSdkOauthSessionEntry, + AnthropicSubscriptionSessionEntry, | "senpiSessionId" | "sdkSessionId" | "sentCount" @@ -121,7 +121,7 @@ export function bindingFromEntry( export async function verifyRestoredTranscript( binding: ContinuityBinding, cwd: string, - authLane: ClaudeSdkOauthAuthLane, + authLane: AnthropicSubscriptionAuthLane, ): Promise { if (authLane === "config-dir") return false; let messages: SessionMessage[]; @@ -158,7 +158,7 @@ export async function verifyRestoredTranscript( return true; } -async function awaitInitialization(entry: ClaudeSdkOauthSessionEntry, signal?: AbortSignal): Promise { +async function awaitInitialization(entry: AnthropicSubscriptionSessionEntry, signal?: AbortSignal): Promise { const initialize = entry.query.initializationResult; if (!initialize) return; if (!signal) { @@ -190,7 +190,7 @@ async function awaitInitialization(entry: ClaudeSdkOauthSessionEntry, signal?: A * `sessionId` must stay absent unless forking, because the SDK rejects the pair * (sdk.d.ts:1805-1808) and would otherwise silently start an unrelated session. */ -export async function reattachSession(input: ReattachInput): Promise { +export async function reattachSession(input: ReattachInput): Promise { // getOrCreateSession() reaches the synchronous SDK `query` through the // session-registry boundary - see sdk-boundary.lazy.ts. await loadClaudeAgentSdk(); diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-pump.ts similarity index 91% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-pump.ts index 8acbdabeca..6967d398e8 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-pump.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-pump.ts @@ -3,8 +3,8 @@ import { refusalError } from "./refusal.ts"; import type { SDKMessage, SDKUserMessage } from "./sdk-boundary.ts"; import { evaluateAbortOutcome } from "./session-reattach.ts"; import { - type ClaudeSdkOauthSessionEntry, - type ClaudeSdkOauthSessionRegistry, + type AnthropicSubscriptionSessionEntry, + type AnthropicSubscriptionSessionRegistry, createSessionUuid, } from "./session-registry.ts"; import { @@ -41,7 +41,7 @@ export class ConcurrentSessionTurnAdmissionError extends Error { } } -function currentTurn(entry: ClaudeSdkOauthSessionEntry): ActiveTurn | null { +function currentTurn(entry: AnthropicSubscriptionSessionEntry): ActiveTurn | null { return entry.activeTurn as ActiveTurn | null; } @@ -57,7 +57,11 @@ function removeAbortListener(turn: ActiveTurn): void { turn.cancelAbort = undefined; } -function failTurn(registry: ClaudeSdkOauthSessionRegistry, entry: ClaudeSdkOauthSessionEntry, error: Error): void { +function failTurn( + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, + error: Error, +): void { const turn = currentTurn(entry); if (turn) { removeAbortListener(turn); @@ -76,8 +80,8 @@ function failTurn(registry: ClaudeSdkOauthSessionRegistry, entry: ClaudeSdkOauth * observation and hand the following turn two. */ function abortTurn( - registry: ClaudeSdkOauthSessionRegistry, - entry: ClaudeSdkOauthSessionEntry, + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, turn: ActiveTurn, _error: Error, ): void { @@ -89,8 +93,8 @@ function abortTurn( } function finishTurn( - registry: ClaudeSdkOauthSessionRegistry, - entry: ClaudeSdkOauthSessionEntry, + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, turn: ActiveTurn, message: Extract, ): void { @@ -110,8 +114,8 @@ function finishTurn( } function handleMessage( - registry: ClaudeSdkOauthSessionRegistry, - entry: ClaudeSdkOauthSessionEntry, + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, message: SDKMessage, ): boolean { // A forked query mints a NEW session id (forkSession: true + resume): the @@ -167,7 +171,10 @@ function handleMessage( return false; } -async function runPump(registry: ClaudeSdkOauthSessionRegistry, entry: ClaudeSdkOauthSessionEntry): Promise { +async function runPump( + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, +): Promise { const iterator = entry.query[Symbol.asyncIterator](); try { while (true) { @@ -184,8 +191,8 @@ async function runPump(registry: ClaudeSdkOauthSessionRegistry, entry: ClaudeSdk } export function submitSessionTurn( - registry: ClaudeSdkOauthSessionRegistry, - entry: ClaudeSdkOauthSessionEntry, + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, request: SessionTurnRequest, limits: PreReplayBufferLimits = { maxMessages: DEFAULT_PRE_REPLAY_MAX_MESSAGES, diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-state.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-state.ts similarity index 88% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-state.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-state.ts index 240a436c27..db284a34a9 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-state.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-state.ts @@ -1,4 +1,4 @@ -export type ClaudeSdkOauthSessionState = +export type AnthropicSubscriptionSessionState = | "ABSENT" | "STARTING" | "IDLE_SYNCED" @@ -12,9 +12,11 @@ export type ClaudeSdkOauthSessionState = | "CLOSED" | "BROKEN"; -type StatefulSession = { state: ClaudeSdkOauthSessionState }; +type StatefulSession = { state: AnthropicSubscriptionSessionState }; -const allowedTransitions: Readonly> = { +const allowedTransitions: Readonly< + Record +> = { ABSENT: ["STARTING"], STARTING: ["IDLE_SYNCED", "TAINTED", "CLOSING", "BROKEN"], IDLE_SYNCED: ["TURN_WAITING", "TAINTED", "CLOSING", "BROKEN"], @@ -29,7 +31,10 @@ const allowedTransitions: Readonly(entry: T, next: ClaudeSdkOauthSessionState): T { +export function transitionSessionState( + entry: T, + next: AnthropicSubscriptionSessionState, +): T { if (!allowedTransitions[entry.state].includes(next)) { throw new Error(`Illegal session state transition: ${entry.state} -> ${next}`); } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-wiring.ts similarity index 98% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-wiring.ts index 6ef2eb6092..365d3952b9 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry-wiring.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry-wiring.ts @@ -1,7 +1,7 @@ import type { AssistantMessage } from "@earendil-works/pi-ai"; import { convertToLlm } from "../../../messages.ts"; import type { ExtensionAPI, ExtensionContext } from "../../types.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "./account-management.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "./account-management.ts"; import { BINDING_ENTRY_TYPE, BINDING_MARKER, @@ -121,7 +121,7 @@ export function registerSessionRegistry( // session closes but the binding stays, so coming back to the same model // reattaches at the recorded prefix instead of re-sending the whole // conversation (senpi#1747). Identity drift still flattens on the way back. - if (event.model?.provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) { + if (event.model?.provider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) { keepBindingThenClose(sessionId, "model_selected"); return; } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry.ts similarity index 90% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry.ts index 7e88f537bf..be9304a784 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-registry.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-registry.ts @@ -11,7 +11,11 @@ import { } from "./session-entry-annotations.ts"; import { recordPendingCloseCause } from "./session-observability.ts"; import { SessionReapScheduler, type SessionRegistryReapHandle } from "./session-reaper.ts"; -import { type ClaudeSdkOauthSessionState, transitionToClosed, transitionToClosing } from "./session-registry-state.ts"; +import { + type AnthropicSubscriptionSessionState, + transitionToClosed, + transitionToClosing, +} from "./session-registry-state.ts"; export const SESSION_REGISTRY_IDLE_TTL_MS = 30 * 60_000; export const SESSION_REGISTRY_MAX_ENTRIES = 32; @@ -79,7 +83,7 @@ export type { SessionRegistryReapHandle }; export type SessionBranchInfo = { oldLeafId: string; newLeafId: string }; -export interface ClaudeSdkOauthSessionEntry { +export interface AnthropicSubscriptionSessionEntry { senpiSessionId: string; sdkSessionId: string; sdkSessionIdConfirmed: boolean; @@ -91,7 +95,7 @@ export interface ClaudeSdkOauthSessionEntry { query: SdkQueryHandle; inputController: SessionInputController; pumpTask: Promise | null; - state: ClaudeSdkOauthSessionState; + state: AnthropicSubscriptionSessionState; activeTurn: unknown | null; syncedPrefixHash: string | null; sentCount: number; @@ -143,12 +147,12 @@ export function createSessionUuid(now = Date.now()): `${string}-${string}-${stri return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`; } -function evictable(entry: ClaudeSdkOauthSessionEntry): boolean { +function evictable(entry: AnthropicSubscriptionSessionEntry): boolean { return (entry.state === "IDLE_SYNCED" || entry.state === "TAINTED") && entry.activeTurn === null; } -export class ClaudeSdkOauthSessionRegistry { - private readonly entries = new Map(); +export class AnthropicSubscriptionSessionRegistry { + private readonly entries = new Map(); /** * Monotonic per-registry generation counter. Numbers are never reused for * a session id, so stale async work gated on `isCurrentGeneration` cannot be @@ -174,11 +178,11 @@ export class ClaudeSdkOauthSessionRegistry { return this.entries.size; } - get(senpiSessionId: string): ClaudeSdkOauthSessionEntry | undefined { + get(senpiSessionId: string): AnthropicSubscriptionSessionEntry | undefined { return this.entries.get(senpiSessionId); } - getOrCreate(input: CreateSessionRegistryEntryInput): ClaudeSdkOauthSessionEntry { + getOrCreate(input: CreateSessionRegistryEntryInput): AnthropicSubscriptionSessionEntry { const existing = this.entries.get(input.senpiSessionId); if (existing) { const now = activeSessionRegistryBoundary.now(); @@ -209,7 +213,7 @@ export class ClaudeSdkOauthSessionRegistry { }, }); const { resume: _resume, ...entryInput } = input; - const target: ClaudeSdkOauthSessionEntry = { + const target: AnthropicSubscriptionSessionEntry = { ...entryInput, sdkSessionId, sdkSessionIdConfirmed: input.resume !== undefined, @@ -227,7 +231,7 @@ export class ClaudeSdkOauthSessionRegistry { pendingForkReason: null, lastUsedAt: now, }; - let entry!: ClaudeSdkOauthSessionEntry; + let entry!: AnthropicSubscriptionSessionEntry; entry = new Proxy(target, { set: (current, property, value) => { if (this.entries.get(current.senpiSessionId) !== entry) return true; @@ -243,7 +247,7 @@ export class ClaudeSdkOauthSessionRegistry { return entry; } - touch(entry: ClaudeSdkOauthSessionEntry): void { + touch(entry: AnthropicSubscriptionSessionEntry): void { this.recordUse(entry, entry.activeTurn === null && evictable(entry)); } @@ -276,7 +280,7 @@ export class ClaudeSdkOauthSessionRegistry { } } - private recordUse(entry: ClaudeSdkOauthSessionEntry, scheduleReap: boolean): void { + private recordUse(entry: AnthropicSubscriptionSessionEntry, scheduleReap: boolean): void { if (this.entries.get(entry.senpiSessionId) !== entry) return; entry.lastUsedAt = activeSessionRegistryBoundary.now(); this.reaper.cancel(entry.senpiSessionId, entry.generation); @@ -293,13 +297,13 @@ export class ClaudeSdkOauthSessionRegistry { } } -export const sessionRegistry = new ClaudeSdkOauthSessionRegistry(); +export const sessionRegistry = new AnthropicSubscriptionSessionRegistry(); -export function getOrCreateSession(input: CreateSessionRegistryEntryInput): ClaudeSdkOauthSessionEntry { +export function getOrCreateSession(input: CreateSessionRegistryEntryInput): AnthropicSubscriptionSessionEntry { return sessionRegistry.getOrCreate(input); } -export function getSession(senpiSessionId: string): ClaudeSdkOauthSessionEntry | undefined { +export function getSession(senpiSessionId: string): AnthropicSubscriptionSessionEntry | undefined { return sessionRegistry.get(senpiSessionId); } @@ -307,7 +311,7 @@ export function closeSession(senpiSessionId: string, reason: string): void { sessionRegistry.closeSession(senpiSessionId, reason); } -export function isIdleExpired(entry: Pick): boolean { +export function isIdleExpired(entry: Pick): boolean { return activeSessionRegistryBoundary.now() - entry.lastUsedAt >= SESSION_REGISTRY_IDLE_TTL_MS; } @@ -332,7 +336,7 @@ export function isCurrentGeneration(senpiSessionId: string, generation: number): } export function isBoundAccountTokenExpiring( - entry: Pick, + entry: Pick, accounts: readonly Pick[], ): boolean { const account = accounts.find((candidate) => candidate.name === entry.accountName); diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-restored-admission.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-restored-admission.ts similarity index 86% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-restored-admission.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-restored-admission.ts index 4ccf4f09c0..015b3f6948 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-restored-admission.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-restored-admission.ts @@ -1,4 +1,4 @@ -import type { ClaudeSdkOauthAuthLane } from "./options.ts"; +import type { AnthropicSubscriptionAuthLane } from "./options.ts"; import { type ContinuityBinding, forgetBinding, getBinding, verifyRestoredTranscript } from "./session-reattach.ts"; export type RestoredBindingAdmission = { @@ -9,7 +9,7 @@ export type RestoredBindingAdmission = { export async function admitRestoredBinding( sessionId: string, cwd: string | undefined, - authLane: ClaudeSdkOauthAuthLane, + authLane: AnthropicSubscriptionAuthLane, ): Promise { const binding = getBinding(sessionId); if (binding?.sentPrefixHash === undefined) return { binding, transcriptAvailable: true }; diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-stream.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-stream.ts similarity index 96% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-stream.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-stream.ts index 695f887788..167332b9d8 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-stream.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-stream.ts @@ -15,7 +15,7 @@ import { } from "./session-observability.ts"; import { bindingFromEntry, bindingInvalidationReason, getBinding, reattachSession } from "./session-reattach.ts"; import { - type ClaudeSdkOauthSessionEntry, + type AnthropicSubscriptionSessionEntry, closeSession, getOrCreateSession, getSession, @@ -30,13 +30,13 @@ import { sentMessages, } from "./session-sync.ts"; import { createSessionTurnAttempt } from "./session-turn-attempt.ts"; -import type { ClaudeSdkOauthProviderSettings } from "./settings.ts"; +import type { AnthropicSubscriptionProviderSettings } from "./settings.ts"; export type ResidentSessionStreamInput = { model: Model; context: Context; streamOptions: SimpleStreamOptions; - providerSettings: ClaudeSdkOauthProviderSettings; + providerSettings: AnthropicSubscriptionProviderSettings; pinnedAccount?: string; buildOptions: Parameters[0]["buildOptions"]; customToolNameToSdk: ReadonlyMap; @@ -61,7 +61,7 @@ function contextHasPriorAssistantMessage(context: Context): boolean { return context.messages.some((message) => message.role === "assistant"); } -function entrySnapshot(entry: ClaudeSdkOauthSessionEntry, hashes: readonly string[]) { +function entrySnapshot(entry: AnthropicSubscriptionSessionEntry, hashes: readonly string[]) { return { sdkSessionId: entry.sdkSessionId, accountName: entry.accountName, @@ -105,7 +105,7 @@ async function createResidentAttempt( let observedReason = "reason" in decision ? decision.reason : decision.kind === "bootstrap" ? "registry_miss" : undefined; let observedKind: "incremental" | "resume" | "cold-seed" = OBSERVED_KIND[decision.kind]; - let entry: ClaudeSdkOauthSessionEntry; + let entry: AnthropicSubscriptionSessionEntry; let from = 0; let flatten = decision.kind === "flatten" || decision.kind === "bootstrap"; diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-sync.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-sync.ts similarity index 93% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-sync.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-sync.ts index 934d633e03..243ce1c34f 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-sync.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-sync.ts @@ -1,9 +1,9 @@ import { createHash } from "node:crypto"; import type { Context, Message } from "@earendil-works/pi-ai"; import { appendSdkContentBlocks } from "./content-blocks.ts"; -import type { ClaudeSdkOauthAuthLane } from "./options.ts"; +import type { AnthropicSubscriptionAuthLane } from "./options.ts"; import type { ContentBlockParam, Options } from "./sdk-boundary.ts"; -import type { ClaudeSdkOauthSessionEntry } from "./session-registry.ts"; +import type { AnthropicSubscriptionSessionEntry } from "./session-registry.ts"; import { HOST_TOOL_POLICY_FINGERPRINT, mapPiToolNameToSdk } from "./tools.ts"; export type SentMessage = Extract; @@ -13,7 +13,7 @@ export type SessionConfigFingerprint = { toolsetHash: string; }; -const sentHashesByEntry = new WeakMap(); +const sentHashesByEntry = new WeakMap(); function stableValue(value: unknown, seen = new WeakSet()): unknown { if (typeof value === "function") return `[function:${value.name}:${value.toString()}]`; @@ -99,11 +99,11 @@ export function sentHashPrefixDigest(hashes: readonly string[], count = hashes.l /** Pure divergence guard: every non-proven continuation resolves to cold-seed. */ -export function sentHashesForEntry(entry: ClaudeSdkOauthSessionEntry): readonly string[] | undefined { +export function sentHashesForEntry(entry: AnthropicSubscriptionSessionEntry): readonly string[] | undefined { return sentHashesByEntry.get(entry); } -export function recordSyncedStream(entry: ClaudeSdkOauthSessionEntry, hashes: readonly string[]): void { +export function recordSyncedStream(entry: AnthropicSubscriptionSessionEntry, hashes: readonly string[]): void { const copy = [...hashes]; sentHashesByEntry.set(entry, copy); entry.sentCount = copy.length; @@ -129,7 +129,7 @@ function fingerprintSystemPrompt(systemPrompt: Options["systemPrompt"]): unknown export function configFingerprint( options: Options, context: Context, - authLane: ClaudeSdkOauthAuthLane, + authLane: AnthropicSubscriptionAuthLane, accountName: string, ): SessionConfigFingerprint { return { diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-attempt.ts similarity index 90% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-attempt.ts index f6c3453998..034c80dea8 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-attempt.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-attempt.ts @@ -3,7 +3,7 @@ import { sdkResultFailure } from "./errors.ts"; import type { SDKMessage, SDKUserMessage } from "./sdk-boundary.ts"; import { bindingFromEntry, forgetBinding, rememberBinding } from "./session-reattach.ts"; import { - type ClaudeSdkOauthSessionEntry, + type AnthropicSubscriptionSessionEntry, closeSession, isCurrentGeneration, sessionRegistry, @@ -20,7 +20,7 @@ function successfulTurn(messages: readonly SDKMessage[]): boolean { ); } -function recordAssistantUuid(entry: ClaudeSdkOauthSessionEntry, sentCount: number, message: SDKMessage): void { +function recordAssistantUuid(entry: AnthropicSubscriptionSessionEntry, sentCount: number, message: SDKMessage): void { if (message.type === "assistant" && message.parent_tool_use_id === null) { entry.assistantUuidByIndex.set(sentCount, message.uuid); } @@ -45,7 +45,7 @@ const RESUME_MESSAGE_MISSING = /no message found with message\.uuid(?:\s+of)?:\s * for the same dead id, fail the same way, and flatten again (senpi#1958); earlier boundaries stay * mapped so the retry can still fork at one of them instead of re-sending the conversation. */ -function forgetMissingAssistantUuid(entry: ClaudeSdkOauthSessionEntry, message: string): void { +function forgetMissingAssistantUuid(entry: AnthropicSubscriptionSessionEntry, message: string): void { const missing = RESUME_MESSAGE_MISSING.exec(message)?.[1]; if (missing === undefined) return; for (const [index, uuid] of entry.assistantUuidByIndex) { @@ -53,11 +53,14 @@ function forgetMissingAssistantUuid(entry: ClaudeSdkOauthSessionEntry, message: } } -function publishBinding(entry: ClaudeSdkOauthSessionEntry, binding: Parameters[0]): void { +function publishBinding( + entry: AnthropicSubscriptionSessionEntry, + binding: Parameters[0], +): void { rememberBinding({ ...binding, sdkSessionIdConfirmed: entry.sdkSessionIdConfirmed }); } -function rememberRetryCheckpoint(entry: ClaudeSdkOauthSessionEntry, hashes: readonly string[]): void { +function rememberRetryCheckpoint(entry: AnthropicSubscriptionSessionEntry, hashes: readonly string[]): void { if (entry.sentCount < 0 || entry.sentCount > hashes.length) return; publishBinding(entry, { ...bindingFromEntry(entry, hashes.slice(0, entry.sentCount)), @@ -66,7 +69,7 @@ function rememberRetryCheckpoint(entry: ClaudeSdkOauthSessionEntry, hashes: read } export function createSessionTurnAttempt( - entry: ClaudeSdkOauthSessionEntry, + entry: AnthropicSubscriptionSessionEntry, message: SDKUserMessage["message"], hashes: readonly string[], signal: AbortSignal | undefined, diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-claim.ts similarity index 82% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-claim.ts index 5bff32b30c..3508e3cc5e 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-claim.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-claim.ts @@ -1,19 +1,19 @@ import { Buffer } from "node:buffer"; import type { SDKMessage } from "./sdk-boundary.ts"; -import type { ClaudeSdkOauthSessionEntry, ClaudeSdkOauthSessionRegistry } from "./session-registry.ts"; +import type { AnthropicSubscriptionSessionEntry, AnthropicSubscriptionSessionRegistry } from "./session-registry.ts"; import { transitionToTurnClaimed, transitionToTurnStreaming } from "./session-registry-state.ts"; import type { ActiveTurn } from "./session-turn-types.ts"; import { SessionTurnAttributionError } from "./session-turn-types.ts"; -export function deliver(entry: ClaudeSdkOauthSessionEntry, turn: ActiveTurn, message: SDKMessage): void { +export function deliver(entry: AnthropicSubscriptionSessionEntry, turn: ActiveTurn, message: SDKMessage): void { if (entry.state === "TURN_CLAIMED") transitionToTurnStreaming(entry); turn.messages.push(message); turn.onMessage?.(message); } export function bufferBeforeReplay( - registry: ClaudeSdkOauthSessionRegistry, - entry: ClaudeSdkOauthSessionEntry, + registry: AnthropicSubscriptionSessionRegistry, + entry: AnthropicSubscriptionSessionEntry, turn: ActiveTurn, message: SDKMessage, ): void { @@ -25,7 +25,7 @@ export function bufferBeforeReplay( if (!registry.isCurrentGeneration(entry.senpiSessionId, turn.generation)) turn.preReplay.length = 0; } -export function claimTurn(entry: ClaudeSdkOauthSessionEntry, turn: ActiveTurn): SDKMessage[] { +export function claimTurn(entry: AnthropicSubscriptionSessionEntry, turn: ActiveTurn): SDKMessage[] { turn.claimed = true; transitionToTurnClaimed(entry); const buffered = turn.preReplay.splice(0); diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-types.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-types.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/session-turn-types.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/session-turn-types.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/settings.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/settings.ts similarity index 77% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/settings.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/settings.ts index dfc65e5a44..9da2389d02 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/settings.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/settings.ts @@ -3,11 +3,11 @@ import { getFileContentRevision } from "../../../../utils/paths.ts"; import { getSettingsPath, type Settings, SettingsManager } from "../../../settings-manager.ts"; import type { SettingSource } from "./sdk-boundary.ts"; -export type ClaudeSdkOauthSystemPromptMode = "preset-append" | "full" | "override"; -export type ClaudeSdkOauthResumeMode = "auto" | "off"; -export type ClaudeSdkOauthTokenInjection = "oauth-slots" | "config-dir" | "ambient"; +export type AnthropicSubscriptionSystemPromptMode = "preset-append" | "full" | "override"; +export type AnthropicSubscriptionResumeMode = "auto" | "off"; +export type AnthropicSubscriptionTokenInjection = "oauth-slots" | "config-dir" | "ambient"; -export interface ClaudeSdkOauthProviderSettings { +export interface AnthropicSubscriptionProviderSettings { /** * Explicit opt-in for the ambient (host Claude CLI) auth lane. Absent means * false: a logged-in host CLI alone never makes this provider available, @@ -17,22 +17,22 @@ export interface ClaudeSdkOauthProviderSettings { */ readonly enabled?: boolean; readonly appendSystemPrompt?: boolean; - readonly systemPromptMode?: ClaudeSdkOauthSystemPromptMode; + readonly systemPromptMode?: AnthropicSubscriptionSystemPromptMode; readonly systemPromptFile?: string; - readonly resumeMode?: ClaudeSdkOauthResumeMode; + readonly resumeMode?: AnthropicSubscriptionResumeMode; readonly settingSources?: SettingSource[]; readonly strictMcpConfig?: boolean; readonly pinnedAccount?: string; - readonly tokenInjection?: ClaudeSdkOauthTokenInjection; + readonly tokenInjection?: AnthropicSubscriptionTokenInjection; } export type ResolvedSystemPromptMode = { - mode: ClaudeSdkOauthSystemPromptMode; + mode: AnthropicSubscriptionSystemPromptMode; source: string; conflict: boolean; }; -type SettingsWithClaudeSdkOauthProvider = Settings & { +type SettingsWithAnthropicSubscriptionProvider = Settings & { // The canonical key after the provider rename (senpi#1989); the legacy key is // still read for two releases for settings that predate the migration. anthropicSubscriptionProvider?: unknown; @@ -41,7 +41,7 @@ type SettingsWithClaudeSdkOauthProvider = Settings & { type Environment = Readonly>; -const systemPromptModeSources = new WeakMap(); +const systemPromptModeSources = new WeakMap(); function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); @@ -61,15 +61,15 @@ function parseEnvironmentSettingSources(value: string | undefined): SettingSourc return parseSettingSources(value.split(",").map((source) => source.trim())); } -function parseSystemPromptMode(value: unknown): ClaudeSdkOauthSystemPromptMode | undefined { +function parseSystemPromptMode(value: unknown): AnthropicSubscriptionSystemPromptMode | undefined { return value === "preset-append" || value === "full" || value === "override" ? value : undefined; } -function parseResumeMode(value: unknown): ClaudeSdkOauthResumeMode | undefined { +function parseResumeMode(value: unknown): AnthropicSubscriptionResumeMode | undefined { return value === "auto" || value === "off" ? value : undefined; } -function parseTokenInjection(value: unknown): ClaudeSdkOauthTokenInjection | undefined { +function parseTokenInjection(value: unknown): AnthropicSubscriptionTokenInjection | undefined { return value === "oauth-slots" || value === "config-dir" || value === "ambient" ? value : undefined; } @@ -91,7 +91,7 @@ function parseNonEmptyString(value: unknown): string | undefined { return typeof value === "string" && value.length > 0 ? value : undefined; } -function parseProviderSettings(value: unknown): ClaudeSdkOauthProviderSettings { +function parseProviderSettings(value: unknown): AnthropicSubscriptionProviderSettings { if (!isRecord(value)) return {}; const enabled = typeof value.enabled === "boolean" ? value.enabled : undefined; const appendSystemPrompt = typeof value.appendSystemPrompt === "boolean" ? value.appendSystemPrompt : undefined; @@ -115,7 +115,7 @@ function parseProviderSettings(value: unknown): ClaudeSdkOauthProviderSettings { }; } -function parseEnvironmentSettings(environment: Environment): ClaudeSdkOauthProviderSettings { +function parseEnvironmentSettings(environment: Environment): AnthropicSubscriptionProviderSettings { const enabled = parseEnvironmentBoolean(environment.SENPI_CLAUDE_SDK_OAUTH_ENABLED); const systemPromptMode = parseSystemPromptMode(environment.SENPI_CLAUDE_SDK_OAUTH_SYSTEM_PROMPT_MODE); const systemPromptFile = parseNonEmptyString(environment.SENPI_CLAUDE_SDK_OAUTH_SYSTEM_PROMPT_FILE); @@ -134,7 +134,7 @@ function parseEnvironmentSettings(environment: Environment): ClaudeSdkOauthProvi }; } -export function resolveSystemPromptMode(settings: ClaudeSdkOauthProviderSettings): ResolvedSystemPromptMode { +export function resolveSystemPromptMode(settings: AnthropicSubscriptionProviderSettings): ResolvedSystemPromptMode { if (settings.systemPromptMode !== undefined) { return { mode: settings.systemPromptMode, @@ -153,12 +153,12 @@ export function resolveSystemPromptMode(settings: ClaudeSdkOauthProviderSettings } /** Load the provider block with env values taking precedence over project and global values. */ -export function loadClaudeSdkOauthProviderSettings( +export function loadAnthropicSubscriptionProviderSettings( settingsManager: SettingsManager, environment: Environment = process.env, -): ClaudeSdkOauthProviderSettings { - const global = settingsManager.getGlobalSettings() as SettingsWithClaudeSdkOauthProvider; - const project = settingsManager.getProjectSettings() as SettingsWithClaudeSdkOauthProvider; +): AnthropicSubscriptionProviderSettings { + const global = settingsManager.getGlobalSettings() as SettingsWithAnthropicSubscriptionProvider; + const project = settingsManager.getProjectSettings() as SettingsWithAnthropicSubscriptionProvider; const environmentSettings = parseEnvironmentSettings(environment); const settings = { ...parseProviderSettings(global.anthropicSubscriptionProvider ?? global.claudeSdkOauthProvider), @@ -173,10 +173,10 @@ function settingsFingerprint(path: string): string { return getFileContentRevision(path) ?? "missing"; } -let cachedClaudeSdkOauthManager: { cwd: string; key: string; manager: SettingsManager } | undefined; +let cachedAnthropicSubscriptionManager: { cwd: string; key: string; manager: SettingsManager } | undefined; /** Load settings from Senpi's configured global and project settings.json paths. */ -export function loadClaudeSdkOauthProviderSettingsFromDisk(cwd: string): ClaudeSdkOauthProviderSettings { +export function loadAnthropicSubscriptionProviderSettingsFromDisk(cwd: string): AnthropicSubscriptionProviderSettings { // fallbackEligible() calls this per candidate probe; cache the manager by // (cwd, settings content revision) and re-apply env live to avoid locked disk reads. const agentDir = getAgentDir(); @@ -184,12 +184,12 @@ export function loadClaudeSdkOauthProviderSettingsFromDisk(cwd: string): ClaudeS getSettingsPath(cwd, agentDir, "project"), )}`; let settingsManager = - cachedClaudeSdkOauthManager?.cwd === cwd && cachedClaudeSdkOauthManager.key === key - ? cachedClaudeSdkOauthManager.manager + cachedAnthropicSubscriptionManager?.cwd === cwd && cachedAnthropicSubscriptionManager.key === key + ? cachedAnthropicSubscriptionManager.manager : undefined; if (!settingsManager) { settingsManager = SettingsManager.create(cwd, agentDir); - cachedClaudeSdkOauthManager = { cwd, key, manager: settingsManager }; + cachedAnthropicSubscriptionManager = { cwd, key, manager: settingsManager }; } - return loadClaudeSdkOauthProviderSettings(settingsManager); + return loadAnthropicSubscriptionProviderSettings(settingsManager); } diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-events.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-events.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-events.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-events.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-guidance.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-guidance.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-guidance.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-guidance.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-protocol.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-protocol.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream-protocol.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream-protocol.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream.ts similarity index 94% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream.ts index 97e9f4e050..f7fe48228d 100644 --- a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/stream.ts +++ b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/stream.ts @@ -12,21 +12,21 @@ import { queryWithAuthLane } from "./auth-lane.ts"; import { buildCustomToolServers } from "./custom-tools.ts"; import { sdkAssistantFailure, sdkResultFailure, sdkResultFailureUsage } from "./errors.ts"; import { defaultExecutableDeps, resolveClaudeCodeExecutable } from "./executable.ts"; -import { buildClaudeSdkOauthQueryOptions } from "./options.ts"; +import { buildAnthropicSubscriptionQueryOptions } from "./options.ts"; import { buildPromptBlocks, buildPromptStream } from "./prompt-bridge.ts"; import { dedupeUltraworkBlocks } from "./prompt-directive-dedupe.ts"; import { refusalError } from "./refusal.ts"; import { getSdkBoundary, loadClaudeAgentSdk, type SdkQueryHandle } from "./sdk-boundary.ts"; import { type ContinuityObservation, emitContinuityObservation } from "./session-observability.ts"; import { residentSessionMessages } from "./session-stream.ts"; -import { loadClaudeSdkOauthProviderSettingsFromDisk } from "./settings.ts"; +import { loadAnthropicSubscriptionProviderSettingsFromDisk } from "./settings.ts"; import { applyStreamEvent } from "./stream-events.ts"; import { withAuthGuidance } from "./stream-guidance.ts"; import { emptyOutput, errorMessage, mapStopReason, type StreamBlock, updateUsage } from "./stream-protocol.ts"; import { toolWatch } from "./tool-watch.ts"; import { resolveSdkTools } from "./tools.ts"; -export function streamClaudeSdkOauth( +export function streamAnthropicSubscription( model: Model, context: Context, options?: SimpleStreamOptions, @@ -68,12 +68,12 @@ export function streamClaudeSdkOauth( const sessionKey = options?.sessionId ? toolWatch.sessionKey(options.sessionId) : undefined; if (sessionKey) toolWatch.reconcileWithContext(sessionKey, context); const toolWatchNote = toolWatch.buildPromptNote(sessionKey, context, resolvedTools.customToolNameToSdk); - const providerSettings = loadClaudeSdkOauthProviderSettingsFromDisk(process.cwd()); + const providerSettings = loadAnthropicSubscriptionProviderSettingsFromDisk(process.cwd()); const toolLessRequest = options?.toolChoice === "none"; const mcpServers = toolLessRequest ? undefined : await buildCustomToolServers(resolvedTools.customTools); const executable = resolveClaudeCodeExecutable(defaultExecutableDeps()); - const buildOptions = (authLane: Parameters[0]["authLane"]) => { - const queryOptions = buildClaudeSdkOauthQueryOptions({ + const buildOptions = (authLane: Parameters[0]["authLane"]) => { + const queryOptions = buildAnthropicSubscriptionQueryOptions({ model, context, streamOptions: options, diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/system-prompt.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/system-prompt.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/system-prompt.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/system-prompt.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tool-watch.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tool-watch.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tool-watch.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tool-watch.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tools.ts b/packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tools.ts similarity index 100% rename from packages/coding-agent/src/core/extensions/builtin/claude-sdk-oauth/tools.ts rename to packages/coding-agent/src/core/extensions/builtin/anthropic-subscription/tools.ts diff --git a/packages/coding-agent/src/core/extensions/builtin/compaction/changes.md b/packages/coding-agent/src/core/extensions/builtin/compaction/changes.md index 8beba822e5..3872e01e53 100644 --- a/packages/coding-agent/src/core/extensions/builtin/compaction/changes.md +++ b/packages/coding-agent/src/core/extensions/builtin/compaction/changes.md @@ -2,7 +2,7 @@ ### What changed -- `packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts`: doc comment names the `anthropic-subscription` main lane. The lane-detection comparisons already route through `CLAUDE_SDK_OAUTH_PROVIDER_ID` and follow the constant's new value; the frozen tokens in this file (`claude-sdk-oauth-compact` entry type, `claude_sdk_oauth_compact_boundary` diagnostic, `senpi.claude-sdk-oauth.compact-boundary.v1` schema) are untouched. +- `packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts`: doc comment names the `anthropic-subscription` main lane. The lane-detection comparisons already route through `ANTHROPIC_SUBSCRIPTION_PROVIDER_ID` and follow the constant's new value; the frozen tokens in this file (`claude-sdk-oauth-compact` entry type, `claude_sdk_oauth_compact_boundary` diagnostic, `senpi.claude-sdk-oauth.compact-boundary.v1` schema) are untouched. - `packages/coding-agent/src/core/extensions/builtin/compaction/speculative-summary.ts`: comment names the provider by its new id. ### Why @@ -1234,8 +1234,8 @@ These are corrections to the lane-policy gate itself, not new behavior an extens ### Scope - Senpi compaction remains FULLY active for every non-`claude-sdk-oauth` provider; that is pinned by the - characterization block in `test/claude-sdk-oauth-compaction-alignment.test.ts`. -- Coverage: `test/compaction/lane-policy.test.ts`, `test/claude-sdk-oauth-compaction-alignment.test.ts`, + characterization block in `test/anthropic-subscription-compaction-alignment.test.ts`. +- Coverage: `test/compaction/lane-policy.test.ts`, `test/anthropic-subscription-compaction-alignment.test.ts`, `test/compaction-checkpoint-oneshot.test.ts`, `test/compaction/checkpoint-directive-characterization.test.ts`. ### Expected merge conflict zones diff --git a/packages/coding-agent/src/core/extensions/builtin/compaction/index.ts b/packages/coding-agent/src/core/extensions/builtin/compaction/index.ts index ead8f8bc57..70c368f3b4 100644 --- a/packages/coding-agent/src/core/extensions/builtin/compaction/index.ts +++ b/packages/coding-agent/src/core/extensions/builtin/compaction/index.ts @@ -24,7 +24,7 @@ import { import * as idle from "./idle.ts"; import * as idleRetry from "./idle-retry.ts"; import { - CLAUDE_SDK_OAUTH_COMPACT_ENTRY_TYPE, + ANTHROPIC_SUBSCRIPTION_COMPACT_ENTRY_TYPE, collectCompactBoundaryEntries, createCompactionLanePolicy, SDK_NATIVE_LANE_REJECTION_REASON, @@ -1063,7 +1063,7 @@ export default function compactionExtension( pi.on("message_end", async (event, ctx) => { for (const entry of collectCompactBoundaryEntries(event.message)) { - pi.appendEntry(CLAUDE_SDK_OAUTH_COMPACT_ENTRY_TYPE, entry); + pi.appendEntry(ANTHROPIC_SUBSCRIPTION_COMPACT_ENTRY_TYPE, entry); } if (isAbortedAssistantMessage(event)) { invalidateSpeculativeCompaction(ctx); diff --git a/packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts b/packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts index 59b4d58059..d60cba2e06 100644 --- a/packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts +++ b/packages/coding-agent/src/core/extensions/builtin/compaction/lane-policy.ts @@ -18,14 +18,14 @@ import type { AgentMessage } from "@earendil-works/pi-agent-core"; import type { AssistantMessageDiagnostic } from "@earendil-works/pi-ai"; import type { CompactionReason } from "../../types.ts"; -import { CLAUDE_SDK_OAUTH_PROVIDER_ID } from "../claude-sdk-oauth/account-management.ts"; -import type { ClaudeSdkOauthProviderSettings } from "../claude-sdk-oauth/settings.ts"; -import { loadClaudeSdkOauthProviderSettingsFromDisk } from "../claude-sdk-oauth/settings.ts"; +import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID } from "../anthropic-subscription/account-management.ts"; +import type { AnthropicSubscriptionProviderSettings } from "../anthropic-subscription/settings.ts"; +import { loadAnthropicSubscriptionProviderSettingsFromDisk } from "../anthropic-subscription/settings.ts"; /** Custom session entry type carrying a mirrored SDK compaction boundary. */ -export const CLAUDE_SDK_OAUTH_COMPACT_ENTRY_TYPE = "claude-sdk-oauth-compact"; +export const ANTHROPIC_SUBSCRIPTION_COMPACT_ENTRY_TYPE = "claude-sdk-oauth-compact"; /** Assistant-message diagnostic the lane uses to transport a received boundary. */ -export const CLAUDE_SDK_OAUTH_COMPACT_BOUNDARY_DIAGNOSTIC = "claude_sdk_oauth_compact_boundary"; +export const ANTHROPIC_SUBSCRIPTION_COMPACT_BOUNDARY_DIAGNOSTIC = "claude_sdk_oauth_compact_boundary"; /** * Reason reported when senpi declines to compact an SDK-native lane. The * `external-owner` rejection cause carries the machine-readable policy while @@ -79,7 +79,7 @@ function isRecord(value: unknown): value is Record { * callers that already know the resolved resume mode never touch disk. */ export function isSdkNativeCompactionLane(input: SdkNativeLaneInput): boolean { - if (input.model?.provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) return false; + if (input.model?.provider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) return false; return input.resumeMode !== "off"; } @@ -89,15 +89,15 @@ export function isSdkNativeCompactionLane(input: SdkNativeLaneInput): boolean { * other providers never pay for the lookup. */ export function createCompactionLanePolicy( - options: { loadProviderSettings?: (cwd: string) => ClaudeSdkOauthProviderSettings } = {}, + options: { loadProviderSettings?: (cwd: string) => AnthropicSubscriptionProviderSettings } = {}, ): CompactionLanePolicy { - const load = options.loadProviderSettings ?? loadClaudeSdkOauthProviderSettingsFromDisk; + const load = options.loadProviderSettings ?? loadAnthropicSubscriptionProviderSettingsFromDisk; let cachedCwd: string | undefined; let cachedResumeMode: string | undefined; // Declared as a local so `ownsCompaction` never depends on `this`: the policy object // is routinely destructured at call sites, which would otherwise unbind the receiver. const disablesSenpiCompaction = (context: LaneContext): boolean => { - if (context.model?.provider !== CLAUDE_SDK_OAUTH_PROVIDER_ID) return false; + if (context.model?.provider !== ANTHROPIC_SUBSCRIPTION_PROVIDER_ID) return false; // A configured compaction model override makes senpi own summarization // for the lane, so the SDK-native stand-down no longer applies. This is // the escape hatch for lanes whose SDK never fires native compaction. @@ -152,7 +152,7 @@ function messageDiagnostics(message: AgentMessage): readonly AssistantMessageDia export function collectCompactBoundaryEntries(message: AgentMessage): CompactBoundaryEntry[] { const entries: CompactBoundaryEntry[] = []; for (const diagnostic of messageDiagnostics(message)) { - if (diagnostic.type !== CLAUDE_SDK_OAUTH_COMPACT_BOUNDARY_DIAGNOSTIC) continue; + if (diagnostic.type !== ANTHROPIC_SUBSCRIPTION_COMPACT_BOUNDARY_DIAGNOSTIC) continue; const entry = parseCompactBoundaryMessage(diagnostic.details); if (entry) entries.push(entry); } diff --git a/packages/coding-agent/src/core/extensions/builtin/compaction/openai-remote-model.ts b/packages/coding-agent/src/core/extensions/builtin/compaction/openai-remote-model.ts index a793f33411..ee7cce8791 100644 --- a/packages/coding-agent/src/core/extensions/builtin/compaction/openai-remote-model.ts +++ b/packages/coding-agent/src/core/extensions/builtin/compaction/openai-remote-model.ts @@ -1,6 +1,6 @@ import { createHash } from "node:crypto"; import { arch, platform, release } from "node:os"; -import { type Api, extractOpenAiCodexAccountId, type Model } from "@earendil-works/pi-ai"; +import { type Api, extractChatGptSubscriptionAccountId, type Model } from "@earendil-works/pi-ai"; export type OpenAiRemoteCompactionModel = Model<"openai-responses"> | Model<"openai-codex-responses">; @@ -36,7 +36,7 @@ function defaultOpenAiRemoteCompactionBaseUrl(model: OpenAiRemoteCompactionModel return model.api === "openai-codex-responses" ? "https://chatgpt.com/backend-api" : "https://api.openai.com/v1"; } -function isTrustedOpenAiCodexBaseUrl(baseUrl: string | undefined): boolean { +function isTrustedChatGptSubscriptionBaseUrl(baseUrl: string | undefined): boolean { try { const url = new URL(baseUrl || "https://chatgpt.com/backend-api"); if (url.protocol === "https:" && url.hostname === "chatgpt.com") return true; @@ -66,7 +66,7 @@ export function isOpenAiRemoteCompactionModel(model: Model | undefined): mo const compat = model.compat as Model<"openai-responses">["compat"] | undefined; if (compat?.supportsRemoteCompactionV2 !== true) return false; } - return identity.api !== "openai-codex-responses" || isTrustedOpenAiCodexBaseUrl(model.baseUrl); + return identity.api !== "openai-codex-responses" || isTrustedChatGptSubscriptionBaseUrl(model.baseUrl); } export function matchesOpenAiRemoteCompactionIdentity( @@ -163,7 +163,7 @@ export function createOpenAiRemoteCompactionHeaders( // This matches the final Codex request contract: header hooks may add // routing headers, but cannot replace configured OAuth identity. headers.set("authorization", `Bearer ${auth.apiKey}`); - const accountId = extractOpenAiCodexAccountId(auth.apiKey); + const accountId = extractChatGptSubscriptionAccountId(auth.apiKey); if (accountId) headers.set("chatgpt-account-id", accountId); else headers.delete("chatgpt-account-id"); } else if (!headers.has("authorization") && auth.apiKey) { diff --git a/packages/coding-agent/src/core/extensions/builtin/cursor-cli-oauth/AGENTS.md b/packages/coding-agent/src/core/extensions/builtin/cursor-cli-oauth/AGENTS.md index 1944f24c3d..03a697599a 100644 --- a/packages/coding-agent/src/core/extensions/builtin/cursor-cli-oauth/AGENTS.md +++ b/packages/coding-agent/src/core/extensions/builtin/cursor-cli-oauth/AGENTS.md @@ -28,7 +28,7 @@ Generated: 2026-08-17 | `models.ts` | Model catalog: cache-first `resolveCursorCliModelCatalog` (TTL cache at `/cursor-cli-oauth/models.json`, ANSI strip, ` -