@oh-my-pi/pi-ai 17.1.2 → 17.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/CHANGELOG.md +54 -0
  2. package/README.md +1 -1
  3. package/dist/types/auth-broker/client.d.ts +7 -1
  4. package/dist/types/auth-broker/remote-store.d.ts +4 -1
  5. package/dist/types/auth-broker/types.d.ts +6 -1
  6. package/dist/types/auth-broker/wire-schemas.d.ts +41 -0
  7. package/dist/types/auth-storage.d.ts +55 -0
  8. package/dist/types/providers/anthropic.d.ts +1 -7
  9. package/dist/types/providers/claude-code-fingerprint.d.ts +15 -0
  10. package/dist/types/providers/cursor.d.ts +30 -7
  11. package/dist/types/providers/openai-codex/request-transformer.d.ts +5 -4
  12. package/dist/types/providers/openai-codex-responses.d.ts +3 -0
  13. package/dist/types/registry/alibaba-token-plan.d.ts +11 -0
  14. package/dist/types/registry/oauth/anthropic-constants.d.ts +12 -0
  15. package/dist/types/registry/oauth/anthropic.d.ts +1 -0
  16. package/dist/types/registry/oauth/index.d.ts +1 -0
  17. package/dist/types/registry/oauth/types.d.ts +8 -0
  18. package/dist/types/types.d.ts +71 -1
  19. package/dist/types/usage/minimax-code.d.ts +1 -0
  20. package/dist/types/usage.d.ts +10 -0
  21. package/dist/types/utils/idle-iterator.d.ts +6 -4
  22. package/package.json +4 -4
  23. package/src/auth-broker/client.ts +24 -1
  24. package/src/auth-broker/remote-store.ts +12 -0
  25. package/src/auth-broker/server.ts +7 -0
  26. package/src/auth-broker/types.ts +7 -0
  27. package/src/auth-broker/wire-schemas.ts +23 -0
  28. package/src/auth-storage.ts +279 -29
  29. package/src/error/flags.ts +1 -1
  30. package/src/providers/__tests__/kimi-code-thinking.test.ts +40 -5
  31. package/src/providers/amazon-bedrock.ts +3 -4
  32. package/src/providers/anthropic.ts +98 -32
  33. package/src/providers/azure-openai-responses.ts +36 -5
  34. package/src/providers/claude-code-fingerprint.ts +19 -0
  35. package/src/providers/cursor.ts +525 -40
  36. package/src/providers/openai-codex/request-transformer.ts +27 -7
  37. package/src/providers/openai-codex-responses.ts +37 -14
  38. package/src/providers/openai-completions.ts +2 -1
  39. package/src/providers/openai-responses.ts +16 -9
  40. package/src/providers/openai-shared.ts +12 -1
  41. package/src/registry/alibaba-token-plan.ts +103 -21
  42. package/src/registry/oauth/anthropic-constants.ts +12 -0
  43. package/src/registry/oauth/anthropic.ts +3 -1
  44. package/src/registry/oauth/index.ts +1 -0
  45. package/src/registry/oauth/types.ts +8 -0
  46. package/src/stream.ts +7 -2
  47. package/src/types.ts +79 -1
  48. package/src/usage/alibaba-token-plan.ts +34 -6
  49. package/src/usage/claude.ts +7 -2
  50. package/src/usage/minimax-code.ts +280 -19
  51. package/src/usage/openai-codex.ts +61 -14
  52. package/src/usage.ts +10 -0
  53. package/src/utils/idle-iterator.ts +8 -6
  54. package/src/utils.ts +5 -5
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [17.1.4] - 2026-07-26
6
+
7
+ ### Added
8
+
9
+ - MiniMax Token Plan accounts now report quota in `omp usage`. `GET /v1/token_plan/remains` returns one bucket per plan quota, each carrying a rolling interval window and a weekly window, so `minimax-code` surfaces real remaining percentages instead of an empty report. A model the plan does not include comes back looking like an untouched quota; those buckets are dropped from the report and named in its metadata. The mainland id `minimax-code-cn` is untouched.
10
+ - OAuth logins now stamp `authorizedAt` (epoch ms of the interactive login) on the stored credential, and every refresh-persist path preserves it. Anthropic expires the whole OAuth grant family ~30 days after authorization regardless of refresh-token rotation (observed as `invalid_grant: "Refresh token expired"` on the latest rotated token, exactly 30 days after login, across four production accounts), so the login anchor is what makes re-login deadlines computable. Exported `ANTHROPIC_OAUTH_GRANT_TTL_MS` alongside the anthropic OAuth flow.
11
+ - Added `GET /v1/credentials/disabled` to the auth broker and `AuthBrokerClient.listDisabledCredentials`: disabled-credential tombstones (`DisabledCredentialSummary` — identity, verbatim disable cause, disable timestamp; never token material) so auto-disabled accounts stay visible to clients instead of silently vanishing from the snapshot. `AuthStorage.listDisabledCredentials` serves the same data locally from SQLite; clients of brokers predating the endpoint get an empty list (404 mapped, no error).
12
+ - Added `AuthStorage.revalidateCredentials()` and the optional `AuthCredentialStore.refreshSnapshot` hook: remote broker stores re-fetch `GET /v1/snapshot` on demand so callers pairing live per-credential data with stored identities (`omp usage`) never render against the up-to-an-hour-stale disk-cached snapshot; local SQLite stores are always current and only reload.
13
+ - Added an optional per-request `codexSseMaxAttempts` stream option to bound Codex SSE pre-response retries while preserving the six-attempt default when omitted.
14
+ - Fixed Cursor requests failing with `Connect error internal: Unable to parse image: ...` whenever the session history contained an image: `rootPromptMessagesJson` image parts now embed a `data:<mime>;base64,` URI instead of bare base64, matching the convention used by the OpenAI-completions provider ([#6564](https://github.com/can1357/oh-my-pi/pull/6564)).
15
+
16
+ ### Fixed
17
+
18
+ - Fixed OpenAI Responses native history replay sending output-only `status` fields back as input, preventing `input[N].status` failures in long-running sessions. ([#6513](https://github.com/can1357/oh-my-pi/pull/6513) by [@Ant39140](https://github.com/Ant39140))
19
+ - Cursor no longer discards a local tool result when the transport fails mid-execution. The provider waits for in-flight exec dispatches before pushing `done`, but the error path skipped that wait, so a handler decoded from the last chunk landed its result after the Agent had already finalized the call from the terminal error and cleared its buffer — losing the real outcome of a tool that may already have run side effects. Both exits now drain the same barrier.
20
+ - Cursor exec handlers returning the bare-result form no longer record a failed call as successful. When an SDK handler returns only a protocol result (no paired `toolResult`), the synthesized transcript entry was always `"Tool produced no transcript result"` with `isError: false`, even for a `rejected` or `error` result — so Cursor saw a failure while the rebuilt transcript showed success. The synthesized entry now derives its state and message from the result's own oneof variant — including MCP, where an application-level tool failure rides inside the `success` variant as `is_error` rather than as a separate variant.
21
+ - Fixed Cursor models silently failing to maintain the todo list. Cursor resolves its native `update_todos`/`read_todos` tools server-side, but the bridge looked for them under flattened `updateTodosToolCall`/`readTodosToolCall` properties, which a decoded `agent.v1.ToolCall` never has — the variant only arrives through the `tool` oneof — so no native todo call was ever recognized. The synthesized `todo` tool call was also emitted as locally runnable with a `{todos}` payload the local tool's schema rejects, so any update that did surface ended as a validation error and local todo state never followed Cursor's. Todo calls are now read from the oneof, both native todo blocks are marked as already-resolved, and local state is mirrored from the server's confirmed success snapshot (leaving state untouched on `UpdateTodosError`). `TODO_STATUS_CANCELLED` now maps to `abandoned` instead of reverting the task to `pending`.
22
+ - Hardened Cursor todo mirroring against partial `read_todos` responses: a read narrowed by `status_filter`/`id_filter`, or one returning fewer rows than the server's own `total_count`, is a subset rather than the list, and is no longer treated as authoritative. Previously such a response would have deleted every task it omitted.
23
+ - Fixed an empty `update_todos` response whose `total_count` is nonzero being mirrored as an authoritative clear, deleting every local task at once. The count-mismatch guard skipped empty responses entirely; only a matching zero count is a genuine clear now. An empty `read_todos` stays refused outright, since proto3 decodes an unset `total_count` as `0` and it cannot be told apart from a filtered read that matched nothing.
24
+ - Fixed a Cursor todo call being left unpaired when the completion frame carried no `tool_call` at all. `ToolCallCompletedUpdate.tool_call` is optional, but the block was already marked as server-resolved by the started frame, so nothing emitted a placeholder for it and every transcript rebuild stripped the interaction. It now settles as "nothing to mirror", the same as a refused snapshot.
25
+ - Fixed local Cursor exec calls (`read`/`write`/`grep`/`delete`/`bash`/`lsp`/MCP) vanishing from rebuilt transcripts when the tool produced no result. The assistant block is synthesized and marked server-resolved before the handler runs, so the three result-less paths — no handler installed, a handler returning nothing, and a thrown handler — left the call unpaired. Each now pairs a result carrying the same text the server receives.
26
+ - Fixed Cursor MCP tool calls being unrecognized on the wire. `ToolCall.tool` is a protobuf oneof, so a decoded message exposes the variant as `{ case, value }` and never as a flat `mcpToolCall` property — the same trap that made native todo calls invisible while hand-shaped fixtures kept passing. Both the streamed start and the completion arg merge now go through a shared selector.
27
+ - Fixed a streamed Cursor MCP block being named from `name` while its paired result used `toolName`, so the two disagreed whenever the server sent different values. Both now prefer `toolName`.
28
+ - Fixed the Cursor stream emitting `done` while a tool handler decoded from the final chunk was still running. Server messages are dispatched fire-and-forget so the socket keeps draining, but nothing waited for them: when an exec request, `turnEnded` and the stream close arrived in one chunk, the turn finished before the handler produced its result, and the result missed the buffer drain that pairs it with its call. In-flight dispatches are now awaited after the transport completes.
29
+ - Fixed a server-resolved Cursor todo call leaving its transcript block stuck pending: the synthetic completion was emitted under a freshly generated id instead of the streamed call id the interactive transcript filed the block under, so the card animated indefinitely. The settled call id is now handed to the sync handler.
30
+ - Fixed server-resolved Cursor todo blocks disappearing from rebuilt transcripts: nothing produced a `toolResult` for them, and `buildSessionContext` strips any `toolCall` left unpaired, so the interaction vanished on reload, branch switch, or transcript rebuild. The result the host builds is now persisted verbatim — it carries the `details.phases` the todo renderer rebuilds the list from, which a summary-only result would have replayed as `0 tasks`.
31
+ - Fixed a refused or failed Cursor todo call leaving its card animating forever. Only a successful snapshot settled the block, so a `read_todos` narrowed by a filter and a server `UpdateTodosError` both went unanswered — no `tool_execution_end`, and no `toolResult` to keep the block from being stripped on rebuild. Every completed native todo call now settles. A server error is carried through as a failed result rather than collapsed into the benign "nothing to mirror" case, which would have replayed the failure as a success.
32
+ - Hardened Cursor todo mirroring against snapshots whose rows collide on content. Cursor's wire model identifies todos by `id` and can represent two rows sharing the same text; the local list is keyed by content alone and the `todo` tool rejects a duplicate outright, so importing such a pair would leave every task-targeted `done`/`drop`/`rm` resolving to the first row and the second unreachable. The snapshot is now refused like any other that cannot be represented locally — local state is left untouched and the call still settles as a no-op.
33
+ - Hardened Cursor todo mirroring against ambiguous empty `read_todos` responses. `total_count` is a proto3 scalar, so an unset field decodes as `0` and is indistinguishable from a genuinely empty list; accepting `todos=[]` + `total_count=0` would clear every local task. Empty and mismatched reads are now refused — `update_todos` remains the authoritative clear path.
34
+ - Fixed refused Cursor todo results claiming `"No todo changes"`. A server-accepted `update_todos` can still be declined locally (content collision, etc.), so the persisted fallback now reads `"Todo snapshot not mirrored"` instead of implying the remote call changed nothing.
35
+ - Hardened Cursor todo mirroring against snapshots carrying unresolved `TodoItem.dependencies`. The wire model blocks a row behind other rows by `id`; the local list has no ids and no edges, so an imported dependent row files as plain `pending` and `nextActionableTask` then offers work the server considers blocked. Snapshots with an edge pointing at a row that is not yet `completed`/`abandoned` are now refused like any other that cannot be represented locally. Edges whose blockers already finished constrain nothing and still mirror.
36
+ - Extended the Cursor todo `total_count` mismatch guard to `update_todos`. A partial or size-limited merge response is as incomplete as a filtered read, but the check only applied to reads, so an update returning fewer rows than its own count was mirrored as the full list and deleted every task it omitted. An empty update still syncs — it remains the authoritative clear path, unlike an ambiguous empty read.
37
+ - Hardened Cursor todo mirroring against rows with empty `content`. `content` is a proto3 string, so a missing or default value arrives as `""`; the local list is keyed by content and rejects a falsy one before lookup, leaving the imported row unreachable to every task-targeted `done`/`drop`/`rm`. Such snapshots are now refused like any other that cannot be represented locally.
38
+ - Fixed a deterministic circular-import TDZ that crashed `packages/catalog`'s test process with `ReferenceError: Cannot access 'claudeCodeVersion' before initialization`: `registry/oauth/anthropic.ts` imported `claudeCodeVersion` from `providers/anthropic.ts`, which transitively pulls the registry back in (`providers/anthropic` → `stream` → `registry` → `registry/oauth/anthropic`), so the module-level `claude-code/${claudeCodeVersion}` bootstrap user-agent const read the binding while `providers/anthropic.ts` was still mid-initialization. `claudeCodeVersion` now lives in a zero-import leaf module (`providers/claude-code-fingerprint.ts`) that `providers/anthropic.ts`, `registry/oauth/anthropic.ts`, and `usage/claude.ts` all import from, removing the cycle at the source rather than deferring the read.
39
+ - Fixed a circular initialization between the Anthropic provider and OAuth registry that could throw before `claudeCodeVersion` was initialized when package tests or consumers loaded modules in parallel ([#6628](https://github.com/can1357/oh-my-pi/pull/6628) by [@anatoli-tsinovoy](https://github.com/anatoli-tsinovoy)).
40
+ - Stopped the account-level Codex `rate_limit.limit_reached` flag from being applied to individual chat windows. Codex reports one shared flag for the whole account, so a window with real headroom was marked `exhausted` because a different window (or a separate metered feature) was at its limit, which over-blocked sibling accounts during credential selection. Each window's status now reflects only its own usage
41
+ - Scoped Codex reactive backoff per meter: a `usage_limit_reached` from a Spark request no longer persists a block that ordinary chat requests honour, and the reverse. Blocks written before scoping used a shared scope meaning "block everything", so requests still honour it and reconciliation still heals it
42
+ - Implemented `scopeLimits` for the Codex ranking strategy so a request gates only on the windows it actually consumes: `-spark` models spend the Spark meter and every other model spends the 5h/weekly chat windows, instead of OR-ing every window and meter into one provider-wide block
43
+ - Fixed native Anthropic adaptive-only models (Opus 4.6+, Sonnet 4.6+, Fable/Mythos 5) keeping thinking ON when reasoning was meant to be off. `mapOptionsForApi` never consulted `disableReasoning` on the Anthropic branch, so a caller-side disable left adaptive thinking at full effort; and `disableThinkingIfToolChoiceForced` deleted `output_config.effort` alongside `thinking`, which for adaptive-only models silently re-enabled adaptive thinking (a bare omission defaults to adaptive-ON). Both paths now omit `thinking` and pin the lowest adaptive effort, so `disableReasoning` and forced `tool_choice` turns (e.g. the delivery reviewer's `report_delivery`) actually suppress reasoning instead of returning a thinking block with `end_turn` ([#6589](https://github.com/can1357/oh-my-pi/issues/6589)).
44
+ - Fixed Bedrock Converse dropping captured Claude thinking signatures when replaying application-inference-profile ARN models, restoring adaptive-thinking multi-turn conversations ([#6610](https://github.com/can1357/oh-my-pi/issues/6610)).
45
+ - Fixed the `alibaba-token-plan` login only supporting the international Singapore endpoint, which rejected China (Beijing) Token Plan `sk-sp-` keys with `401 invalid_api_key`. Login now selects a region (International / China (Beijing) / Custom), validates the key against that region's `/models` endpoint, and stores the chosen base URL in the credential so inference and discovery both target it ([#6682](https://github.com/can1357/oh-my-pi/issues/6682)).
46
+ - Fixed statusless provider capacity errors such as `no_capacity` and high-demand responses being treated as terminal instead of retryable. ([#6503](https://github.com/can1357/oh-my-pi/issues/6503))
47
+ - Fixed QwenCloud Token Plan quota reporting to call the current console usage RPC and document how to capture its optional Cookie during login.
48
+ - Fixed Cursor exec-channel MCP calls such as `web_search` omitting `toolCall` blocks when no interaction block arrives, which rendered their tool cards below the final assistant answer or dropped them on transcript replay. ([#6501](https://github.com/can1357/oh-my-pi/issues/6501))
49
+ - Fixed Claude scoped weekly limits (e.g. `Claude 7 Day (Fable)`) with `is_active: false` being dropped by the `/usage` parser, rendering as `not reported` in `omp usage` despite carrying real utilization. Live payloads mark only the currently binding limit active — an account pinned at a 100% Fable cap reports its 77% shared weekly row as inactive too — so `is_active` signals severity ranking, not bucket existence, and is now ignored. Exhaustion gating is unchanged: tier rows still hard-block only at confirmed 100% with a future reset.
50
+ - Fixed a TDZ crash (`Cannot access 'claudeCodeVersion' before initialization`) when `providers/anthropic` was the first module loaded: `providers/anthropic` → `stream` → `registry` → `registry/oauth/anthropic` circled back into the still-initializing provider module. The Claude Code fingerprint constants now live in the leaf module `providers/claude-code-fingerprint` (star re-exported from `providers/anthropic`, so import paths are unchanged).
51
+
52
+ ## [17.1.3] - 2026-07-24
53
+
54
+ ### Fixed
55
+
56
+ - Fixed Cursor sessions exposing `ast_edit` (and other staged-preview `xd://` devices) without a reachable resolver: the built-in `write` tool — which carries the `xd://resolve` / `xd://reject` transport that finalizes a staged preview — was filtered out of Cursor's forwarded catalog, so previews could never be resolved and the session aborted after three forced `write` turns. `write` is now re-included in the forwarded catalog whenever pi-agent devices are advertised ([#6536](https://github.com/can1357/oh-my-pi/issues/6536)).
57
+ - Fixed OpenAI Responses and chat-completions streams honoring per-model first-event watchdog policy, allowing local llama.cpp-style backends to process arbitrarily large prompts without a premature client cancellation ([#6524](https://github.com/can1357/oh-my-pi/issues/6524)).
58
+
5
59
  ## [17.1.2] - 2026-07-24
6
60
 
7
61
  ### Added
package/README.md CHANGED
@@ -73,7 +73,7 @@ Unified LLM API with automatic model discovery, provider configuration, token an
73
73
  - **Xiaomi MiMo** (requires `XIAOMI_API_KEY`)
74
74
  - **ZenMux** (requires `ZENMUX_API_KEY`)
75
75
  - **Qwen Portal** (supports `QWEN_OAUTH_TOKEN` or `QWEN_PORTAL_API_KEY`)
76
- - **QwenCloud Token Plan** (supports `/login alibaba-token-plan`, `ALIBABA_TOKEN_PLAN_API_KEY`, or `BAILIAN_TOKEN_PLAN_API_KEY`; interactive login optionally stores a `home.qwencloud.com` Cookie request header for best-effort 5-hour and 7-day quota reporting)
76
+ - **QwenCloud Token Plan** (supports `/login alibaba-token-plan`, `ALIBABA_TOKEN_PLAN_API_KEY`, or `BAILIAN_TOKEN_PLAN_API_KEY`; interactive login first selects a region — International (Singapore, default), China (Beijing) for 百炼 Token Plan keys, or a custom base URL — since region keys are non-interchangeable, then optionally stores a `home.qwencloud.com` Cookie request header for best-effort 5-hour and 7-day quota reporting)
77
77
  To enable quota reporting, sign in to the Token Plan dashboard, copy the `Cookie` request-header value from a `home.qwencloud.com` request in browser developer tools, and paste it at the second login prompt. Press Enter to skip; the Cookie is sensitive and session-lived, so rerun login when it expires.
78
78
  - **Cloudflare AI Gateway** (requires `CLOUDFLARE_AI_GATEWAY_API_KEY` and provider-specific gateway base URL)
79
79
  - **Ollama** (local OpenAI-compatible runtime; optional `OLLAMA_API_KEY`)
@@ -1,4 +1,4 @@
1
- import type { AuthCredential } from "../auth-storage.js";
1
+ import type { AuthCredential, DisabledCredentialSummary } from "../auth-storage.js";
2
2
  import type { ClientUsageReportRequest, ClientUsageReportResponse, ClientUsageSummaryResponse, CredentialBlockRequest, CredentialBlockResponse, CredentialBlocksDeleteResponse, CredentialDisableResponse, CredentialRefreshResponse, CredentialUploadResponse, HealthzResponse, SnapshotResponse, SnapshotStreamEvent, UsageHistoryResponse, UsageResponse, UsageStaleResponse } from "./types.js";
3
3
  export interface AuthBrokerClientOptions {
4
4
  /** Base URL (e.g. `https://broker.tailnet:8765`). Trailing slashes are trimmed. */
@@ -74,6 +74,12 @@ export declare class AuthBrokerClient {
74
74
  notifyUsageStale(signal?: AbortSignal): Promise<UsageStaleResponse>;
75
75
  refreshCredential(id: number, signal?: AbortSignal): Promise<CredentialRefreshResponse>;
76
76
  disableCredential(id: number, cause: string, signal?: AbortSignal): Promise<CredentialDisableResponse>;
77
+ /**
78
+ * Disabled-credential tombstones (identity + cause, no token material).
79
+ * Returns an empty list against brokers predating `GET
80
+ * /v1/credentials/disabled` (404).
81
+ */
82
+ listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
77
83
  uploadCredential(provider: string, credential: AuthCredential, signal?: AbortSignal): Promise<CredentialUploadResponse>;
78
84
  upsertCredentialBlock(id: number, block: CredentialBlockRequest, signal?: AbortSignal): Promise<CredentialBlockResponse>;
79
85
  deleteCredentialBlocks(id: number, signal?: AbortSignal): Promise<CredentialBlocksDeleteResponse>;
@@ -1,4 +1,4 @@
1
- import { type AuthCredential, type AuthCredentialStore, type OAuthCredential, type StoredAuthCredential, type StoredCredentialBlock } from "../auth-storage.js";
1
+ import { type AuthCredential, type AuthCredentialStore, type DisabledCredentialSummary, type OAuthCredential, type StoredAuthCredential, type StoredCredentialBlock } from "../auth-storage.js";
2
2
  import type { OAuthCredentials } from "../registry/oauth/types.js";
3
3
  import type { Provider } from "../types.js";
4
4
  import type { ObservedUsageEntry, UsageReport } from "../usage.js";
@@ -44,10 +44,13 @@ export declare class RemoteAuthCredentialStore implements AuthCredentialStore {
44
44
  /** Re-hydrate the in-memory snapshot from the broker. */
45
45
  refreshSnapshot(): Promise<SnapshotResponse>;
46
46
  listAuthCredentials(provider?: string): StoredAuthCredential[];
47
+ /** Broker-backed disabled tombstones; empty against brokers predating the endpoint. */
48
+ listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
47
49
  getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined;
48
50
  getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined;
49
51
  listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[];
50
52
  upsertCredentialBlock(block: StoredCredentialBlock): void;
53
+ deleteCredentialBlock(_credentialId: number, _providerKey: string, _blockScope: string): void;
51
54
  deleteCredentialBlocks(credentialId: number): void;
52
55
  cleanExpiredCredentialBlocks(nowMs: number): void;
53
56
  /**
@@ -5,7 +5,7 @@
5
5
  * clients use `access` tokens directly and call back to the broker when a
6
6
  * credential expires or a 401 surfaces on a supposedly-fresh credential.
7
7
  */
8
- import type { AuthCredential, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, StoredCredentialBlock } from "../auth-storage.js";
8
+ import type { AuthCredential, AuthCredentialSnapshot, AuthCredentialSnapshotEntry, DisabledCredentialSummary, StoredCredentialBlock } from "../auth-storage.js";
9
9
  import type { ClientUsageClientSummary, ClientUsageReport, UsageHistoryEntry, UsageReport } from "../usage.js";
10
10
  /** GET /v1/healthz response body. */
11
11
  export interface HealthzResponse {
@@ -66,6 +66,11 @@ export interface CredentialDisableRequest {
66
66
  export interface CredentialDisableResponse {
67
67
  ok: boolean;
68
68
  }
69
+ /** GET /v1/credentials/disabled response body — tombstones of auto-disabled rows. */
70
+ export interface DisabledCredentialsResponse {
71
+ generatedAt: number;
72
+ disabled: DisabledCredentialSummary[];
73
+ }
69
74
  /** POST /v1/credential/:id/block request body. */
70
75
  export type CredentialBlockRequest = CredentialBlockSnapshot;
71
76
  /** POST /v1/credential/:id/block response body. */
@@ -11,6 +11,7 @@ export declare const oauthCredentialSchema: import("arktype/internal/variants/ob
11
11
  accountId?: string | undefined;
12
12
  orgId?: string | undefined;
13
13
  orgName?: string | undefined;
14
+ authorizedAt?: number | undefined;
14
15
  }, {}>;
15
16
  /** OAuth credential as it appears in broker snapshots — refresh replaced with sentinel. */
16
17
  export declare const remoteOauthCredentialSchema: import("arktype/internal/variants/object.ts").ObjectType<{
@@ -25,6 +26,7 @@ export declare const remoteOauthCredentialSchema: import("arktype/internal/varia
25
26
  accountId?: string | undefined;
26
27
  orgId?: string | undefined;
27
28
  orgName?: string | undefined;
29
+ authorizedAt?: number | undefined;
28
30
  }, {}>;
29
31
  export declare const apiKeyCredentialSchema: import("arktype/internal/variants/object.ts").ObjectType<{
30
32
  type: "api_key";
@@ -44,6 +46,7 @@ export declare const writableAuthCredentialSchema: import("arktype/internal/vari
44
46
  accountId?: string | undefined;
45
47
  orgId?: string | undefined;
46
48
  orgName?: string | undefined;
49
+ authorizedAt?: number | undefined;
47
50
  } | {
48
51
  type: "api_key";
49
52
  key: string;
@@ -62,6 +65,7 @@ export declare const snapshotCredentialSchema: import("arktype/internal/variants
62
65
  accountId?: string | undefined;
63
66
  orgId?: string | undefined;
64
67
  orgName?: string | undefined;
68
+ authorizedAt?: number | undefined;
65
69
  } | {
66
70
  type: "api_key";
67
71
  key: string;
@@ -82,6 +86,7 @@ export declare const credentialSnapshotEntrySchema: import("arktype/internal/var
82
86
  accountId?: string | undefined;
83
87
  orgId?: string | undefined;
84
88
  orgName?: string | undefined;
89
+ authorizedAt?: number | undefined;
85
90
  } | {
86
91
  type: "api_key";
87
92
  key: string;
@@ -110,6 +115,7 @@ export declare const snapshotEntrySchema: import("arktype/internal/variants/obje
110
115
  accountId?: string | undefined;
111
116
  orgId?: string | undefined;
112
117
  orgName?: string | undefined;
118
+ authorizedAt?: number | undefined;
113
119
  } | {
114
120
  type: "api_key";
115
121
  key: string;
@@ -155,6 +161,7 @@ export declare const snapshotResponseSchema: import("arktype/internal/variants/o
155
161
  accountId?: string | undefined;
156
162
  orgId?: string | undefined;
157
163
  orgName?: string | undefined;
164
+ authorizedAt?: number | undefined;
158
165
  } | {
159
166
  type: "api_key";
160
167
  key: string;
@@ -196,6 +203,7 @@ export declare const snapshotStreamSnapshotEventSchema: import("arktype/internal
196
203
  accountId?: string | undefined;
197
204
  orgId?: string | undefined;
198
205
  orgName?: string | undefined;
206
+ authorizedAt?: number | undefined;
199
207
  } | {
200
208
  type: "api_key";
201
209
  key: string;
@@ -238,6 +246,7 @@ export declare const snapshotStreamEntryEventSchema: import("arktype/internal/va
238
246
  accountId?: string | undefined;
239
247
  orgId?: string | undefined;
240
248
  orgName?: string | undefined;
249
+ authorizedAt?: number | undefined;
241
250
  } | {
242
251
  type: "api_key";
243
252
  key: string;
@@ -292,6 +301,7 @@ export declare const snapshotStreamEventSchema: import("arktype/internal/variant
292
301
  accountId?: string | undefined;
293
302
  orgId?: string | undefined;
294
303
  orgName?: string | undefined;
304
+ authorizedAt?: number | undefined;
295
305
  } | {
296
306
  type: "api_key";
297
307
  key: string;
@@ -332,6 +342,7 @@ export declare const snapshotStreamEventSchema: import("arktype/internal/variant
332
342
  accountId?: string | undefined;
333
343
  orgId?: string | undefined;
334
344
  orgName?: string | undefined;
345
+ authorizedAt?: number | undefined;
335
346
  } | {
336
347
  type: "api_key";
337
348
  key: string;
@@ -489,6 +500,7 @@ export declare const credentialRefreshResponseSchema: import("arktype/internal/v
489
500
  accountId?: string | undefined;
490
501
  orgId?: string | undefined;
491
502
  orgName?: string | undefined;
503
+ authorizedAt?: number | undefined;
492
504
  } | {
493
505
  type: "api_key";
494
506
  key: string;
@@ -503,6 +515,33 @@ export declare const credentialDisableRequestSchema: import("arktype/internal/va
503
515
  export declare const credentialDisableResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
504
516
  ok: boolean;
505
517
  }, {}>;
518
+ /** One disabled-credential tombstone — identity + cause, never token material. */
519
+ export declare const disabledCredentialSummarySchema: import("arktype/internal/variants/object.ts").ObjectType<{
520
+ id: number;
521
+ provider: string;
522
+ type: "api_key" | "oauth";
523
+ email?: string | undefined;
524
+ accountId?: string | undefined;
525
+ orgId?: string | undefined;
526
+ orgName?: string | undefined;
527
+ cause: string;
528
+ disabledAtMs?: number | undefined;
529
+ }, {}>;
530
+ /** Broker `GET /v1/credentials/disabled` response. */
531
+ export declare const disabledCredentialsResponseSchema: import("arktype/internal/variants/object.ts").ObjectType<{
532
+ generatedAt: number;
533
+ disabled: {
534
+ id: number;
535
+ provider: string;
536
+ type: "api_key" | "oauth";
537
+ email?: string | undefined;
538
+ accountId?: string | undefined;
539
+ orgId?: string | undefined;
540
+ orgName?: string | undefined;
541
+ cause: string;
542
+ disabledAtMs?: number | undefined;
543
+ }[];
544
+ }, {}>;
506
545
  export declare const credentialBlockRequestSchema: import("arktype/internal/variants/object.ts").ObjectType<{
507
546
  providerKey: string;
508
547
  blockScope: string;
@@ -532,6 +571,7 @@ export declare const credentialUploadRequestSchema: import("arktype/internal/var
532
571
  accountId?: string | undefined;
533
572
  orgId?: string | undefined;
534
573
  orgName?: string | undefined;
574
+ authorizedAt?: number | undefined;
535
575
  } | {
536
576
  type: "api_key";
537
577
  key: string;
@@ -554,6 +594,7 @@ export declare const credentialUploadResponseSchema: import("arktype/internal/va
554
594
  accountId?: string | undefined;
555
595
  orgId?: string | undefined;
556
596
  orgName?: string | undefined;
597
+ authorizedAt?: number | undefined;
557
598
  } | {
558
599
  type: "api_key";
559
600
  key: string;
@@ -75,6 +75,27 @@ export interface StoredCredentialBlock {
75
75
  /** Last row update timestamp in epoch milliseconds, when provided by the backing store. */
76
76
  updatedAtMs?: number;
77
77
  }
78
+ /**
79
+ * Identity slice of a disabled (soft-deleted) credential tombstone — cause and
80
+ * account identity only, never token material. Surfaced so auto-disabled
81
+ * accounts (e.g. an expired Anthropic OAuth grant) stay visible in `omp usage`
82
+ * instead of silently vanishing until the user notices missing quota.
83
+ */
84
+ export interface DisabledCredentialSummary {
85
+ /** Database row id (matches {@link StoredAuthCredential.id}). */
86
+ id: number;
87
+ provider: string;
88
+ type: AuthCredential["type"];
89
+ email?: string;
90
+ accountId?: string;
91
+ /** Organization/workspace the credential was scoped to (Anthropic/ChatGPT multi-subscription). */
92
+ orgId?: string;
93
+ orgName?: string;
94
+ /** Verbatim disable cause captured when the row was torn down. */
95
+ cause: string;
96
+ /** Epoch ms the row was disabled (SQLite `updated_at`), when known. */
97
+ disabledAtMs?: number;
98
+ }
78
99
  /**
79
100
  * Per-credential health record returned by {@link AuthStorage.checkCredentials}.
80
101
  *
@@ -239,6 +260,21 @@ export interface AuthCredentialStore {
239
260
  /** Optional hook to notify the underlying store that usage report cache is stale. */
240
261
  invalidateUsageCache?(signal?: AbortSignal): Promise<void>;
241
262
  listAuthCredentials(provider?: string): StoredAuthCredential[];
263
+ /**
264
+ * Optional store hook to re-hydrate the credential snapshot from its
265
+ * backing source. Remote broker stores re-fetch `GET /v1/snapshot` so a
266
+ * disk-cached snapshot (up to an hour stale) cannot be paired with live
267
+ * per-credential data; local SQLite stores omit it — their reads are
268
+ * always current.
269
+ */
270
+ refreshSnapshot?(): Promise<unknown>;
271
+ /**
272
+ * Disabled credential tombstones (see {@link DisabledCredentialSummary}).
273
+ * Optional: remote stores forward to the broker's
274
+ * `GET /v1/credentials/disabled` (empty list when the broker predates the
275
+ * endpoint); stores without tombstones omit it.
276
+ */
277
+ listDisabledCredentials?(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
242
278
  updateAuthCredential(id: number, credential: AuthCredential): void;
243
279
  deleteAuthCredential(id: number, disabledCause: string): void;
244
280
  tryDisableAuthCredentialIfMatches(id: number, expectedData: string, disabledCause: string, lease?: CredentialRefreshLeaseFence): boolean;
@@ -259,6 +295,8 @@ export interface AuthCredentialStore {
259
295
  getCredentialBlockReconcileAfter?(credentialId: number, providerKey: string, blockScope: string): number | undefined;
260
296
  /** Upsert with MAX semantics: keep the later blockedUntilMs on conflict. */
261
297
  upsertCredentialBlock?(block: StoredCredentialBlock): void;
298
+ /** Drop one block row for a credential/provider/scope key. */
299
+ deleteCredentialBlock?(credentialId: number, providerKey: string, blockScope: string): void;
262
300
  /** Drop every block row for a credential (all providerKeys/scopes). */
263
301
  deleteCredentialBlocks?(credentialId: number): void;
264
302
  /** Prune rows with blocked_until_ms <= nowMs. */
@@ -1119,6 +1157,20 @@ export declare class AuthStorage {
1119
1157
  * (the broker server's HTTP handler does this).
1120
1158
  */
1121
1159
  exportSnapshot(): AuthCredentialSnapshot;
1160
+ /**
1161
+ * Disabled credential tombstones for display surfaces (`omp usage`,
1162
+ * broker `GET /v1/credentials/disabled`). Empty when the backing store
1163
+ * keeps no tombstones or the remote broker predates the endpoint.
1164
+ */
1165
+ listDisabledCredentials(provider?: string, signal?: AbortSignal): Promise<DisabledCredentialSummary[]>;
1166
+ /**
1167
+ * Force the backing store to revalidate its credential snapshot, then
1168
+ * reload. Remote broker stores re-fetch the snapshot; local stores are
1169
+ * always current, so only the reload runs. Callers that pair live
1170
+ * per-credential data with stored identities (`omp usage`) use this so a
1171
+ * disk-cached snapshot cannot misattribute fresh reports.
1172
+ */
1173
+ revalidateCredentials(): Promise<void>;
1122
1174
  /**
1123
1175
  * Refresh the OAuth credential with the given id through a per-credential
1124
1176
  * single-flight. Concurrent callers for the same row await the same upstream
@@ -1162,6 +1214,7 @@ export declare class AuthStorage {
1162
1214
  /**
1163
1215
  * Broker-server seam: clear all persisted blocks for one credential and notify snapshot waiters.
1164
1216
  */
1217
+ deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void;
1165
1218
  deleteCredentialBlocks(credentialId: number): void;
1166
1219
  /**
1167
1220
  * Describe where the active credential for a provider came from.
@@ -1198,6 +1251,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
1198
1251
  constructor(db: Database);
1199
1252
  static open(dbPath?: string): Promise<SqliteAuthCredentialStore>;
1200
1253
  listAuthCredentials(provider?: string): StoredAuthCredential[];
1254
+ listDisabledCredentials(provider?: string): Promise<DisabledCredentialSummary[]>;
1201
1255
  replaceAuthCredentialsForProvider(provider: string, credentials: AuthCredential[]): StoredAuthCredential[];
1202
1256
  upsertAuthCredentialForProvider(provider: string, credential: AuthCredential): StoredAuthCredential[];
1203
1257
  updateAuthCredential(id: number, credential: AuthCredential): void;
@@ -1221,6 +1275,7 @@ export declare class SqliteAuthCredentialStore implements AuthCredentialStore {
1221
1275
  getCredentialBlock(credentialId: number, providerKey: string, blockScope: string): number | undefined;
1222
1276
  getCredentialBlockReconcileAfter(credentialId: number, providerKey: string, blockScope: string): number | undefined;
1223
1277
  upsertCredentialBlock(block: StoredCredentialBlock): void;
1278
+ deleteCredentialBlock(credentialId: number, providerKey: string, blockScope: string): void;
1224
1279
  deleteCredentialBlocks(credentialId: number): void;
1225
1280
  cleanExpiredCredentialBlocks(nowMs: number): void;
1226
1281
  listCredentialBlocks(credentialIds: readonly number[]): StoredCredentialBlock[];
@@ -26,12 +26,7 @@ type AnthropicCacheControl = NonNullable<TextBlockParam["cache_control"]>;
26
26
  * hasn't been materialized yet.
27
27
  */
28
28
  export declare function clearAnthropicFastModeFallback(providerSessionState: Map<string, ProviderSessionState> | undefined): void;
29
- export declare const claudeCodeVersion = "2.1.165";
30
- export declare const claudeAgentSdkVersion = "0.3.165";
31
- export declare const claudeClientVersion = "1.11187.4";
32
- export declare const claudeToolPrefix: string;
33
- export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
34
- export declare const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;
29
+ export * from "./claude-code-fingerprint.js";
35
30
  export declare function mapStainlessOs(platform: string): "MacOS" | "Windows" | "Linux" | "FreeBSD" | `Other::${string}`;
36
31
  export declare function mapStainlessArch(arch: string): "x64" | "arm64" | "x86" | `other::${string}`;
37
32
  export declare const claudeCodeHeaders: {
@@ -258,4 +253,3 @@ export declare function convertAnthropicMessages(messages: Message[], model: Mod
258
253
  serverSideFallbackEnabled?: boolean;
259
254
  }): AnthropicMessageParam[];
260
255
  export declare function normalizeAnthropicToolSchema(schema: unknown): unknown;
261
- export {};
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Claude Code stealth-fingerprint constants, kept in a leaf module so
3
+ * fingerprint consumers outside the provider (`registry/oauth/anthropic`,
4
+ * `usage/claude`) don't import the heavy `providers/anthropic` module.
5
+ * That import edge was a live init cycle: `providers/anthropic` → `stream` →
6
+ * `registry` → `registry/oauth/anthropic` → back into the still-initializing
7
+ * provider module, which threw a TDZ ReferenceError whenever
8
+ * `providers/anthropic` was the first module loaded.
9
+ */
10
+ export declare const claudeCodeVersion = "2.1.165";
11
+ export declare const claudeAgentSdkVersion = "0.3.165";
12
+ export declare const claudeClientVersion = "1.11187.4";
13
+ export declare const claudeToolPrefix: string;
14
+ export declare const claudeCodeSystemInstruction = "You are a Claude agent, built on Anthropic's Claude Agent SDK.";
15
+ export declare const CLAUDE_CODE_MAX_OUTPUT_TOKENS = 64000;
@@ -2,7 +2,7 @@ import http2 from "node:http2";
2
2
  import { type JsonValue } from "@bufbuild/protobuf";
3
3
  import type { McpToolDefinition } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
4
4
  import { type AgentServerMessage, type ConversationStateStructure } from "@oh-my-pi/pi-catalog/discovery/cursor-gen/agent_pb";
5
- import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorToolResultHandler, Message, StreamFunction, StreamOptions, TextContent, ThinkingContent, ToolCall, ToolResultMessage } from "../types.js";
5
+ import type { AssistantMessage, CursorExecHandlerResult, CursorExecHandlers, CursorExecPairing, CursorTodoSyncHandler, CursorToolResultHandler, Message, StreamFunction, StreamOptions, TextContent, ThinkingContent, Tool, ToolCall, ToolResultMessage } from "../types.js";
6
6
  import { kCursorExecResolved, kStreamingBlockIndex, kStreamingBlockKind, kStreamingLastParseLen, kStreamingPartialJson } from "../utils/block-symbols.js";
7
7
  import { AssistantMessageEventStream } from "../utils/event-stream.js";
8
8
  export declare const CURSOR_API_URL = "https://api2.cursor.sh";
@@ -43,7 +43,7 @@ export interface BlockState {
43
43
  [kStreamingBlockIndex]: number;
44
44
  }) | null;
45
45
  currentToolCall: ToolCallState | null;
46
- /** MCP call IDs executed through Cursor's exec channel before their stream block arrives. */
46
+ /** MCP call IDs synthesized from exec frames before their redundant streamed block arrives. */
47
47
  resolvedMcpToolCallIds: Set<string>;
48
48
  firstTokenTime: number | undefined;
49
49
  setTextBlock: (b: (TextContent & {
@@ -54,14 +54,36 @@ export interface BlockState {
54
54
  }) | null) => void;
55
55
  setToolCall: (t: ToolCallState | null) => void;
56
56
  setFirstTokenTime: () => void;
57
+ /** Mirror a server-confirmed todo snapshot into local session state. */
58
+ onTodoSnapshot?: CursorTodoSyncHandler;
59
+ /**
60
+ * Persist a paired `toolResult` for a server-resolved call. Native todo calls
61
+ * never travel the exec channel, so without this the resolved block has no
62
+ * matching result and every transcript rebuild strips it as dangling.
63
+ */
64
+ onToolResult?: CursorToolResultHandler;
57
65
  }
58
66
  export interface UsageState {
59
67
  sawTokenDelta: boolean;
60
68
  }
61
69
  /** Exported for tests: drives one Cursor server message through the stream (exec waits mark the stream busy). */
62
70
  export declare function handleServerMessage(msg: AgentServerMessage, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, blobStore: Map<string, Uint8Array>, h2Request: http2.ClientHttp2Stream, execHandlers: CursorExecHandlers | undefined, onToolResult: CursorToolResultHandler | undefined, usageState: UsageState, requestContextTools: McpToolDefinition[], onConversationCheckpoint?: (checkpoint: ConversationStateStructure) => void): Promise<void>;
63
- /** Exported for tests: verifies handler is invoked with correct `this` when passed as bound. */
64
- export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler: ((args: TArgs) => Promise<CursorExecHandlerResult<TResult>>) | undefined, onToolResult: CursorToolResultHandler | undefined, buildFromToolResult: (toolResult: ToolResultMessage) => TResult, buildRejected: (reason: string) => TResult, buildError: (error: string) => TResult): Promise<{
71
+ /**
72
+ * Exported for tests: verifies handler is invoked with correct `this` when passed as bound.
73
+ *
74
+ * Every exit pairs a `toolResult`. The synthesized block was already marked
75
+ * `kCursorExecResolved` before this runs (`synthesizeCursorExecToolCall`), so
76
+ * `agent-loop.ts` emits no placeholder for it: a path that returns without a
77
+ * result leaves the call unpaired and `buildSessionContext` strips the whole
78
+ * interaction on replay. The three result-less paths — no handler installed, a
79
+ * handler that produced nothing, and a thrown handler — therefore synthesize
80
+ * one from the same text the server sees in `execResult`.
81
+ *
82
+ * `pairing` is required so a new callsite cannot silently recreate the orphan,
83
+ * and nullable for the one caller whose block is NOT pre-resolved: MCP without
84
+ * an `mcp` handler, which `agent-loop.ts` runs locally and pairs itself.
85
+ */
86
+ export declare function resolveExecHandler<TArgs, TResult>(args: TArgs, handler: ((args: TArgs) => Promise<CursorExecHandlerResult<TResult>>) | undefined, onToolResult: CursorToolResultHandler | undefined, buildFromToolResult: (toolResult: ToolResultMessage) => TResult, buildRejected: (reason: string) => TResult, buildError: (error: string) => TResult, pairing: CursorExecPairing | null): Promise<{
65
87
  execResult: TResult;
66
88
  toolResult?: ToolResultMessage;
67
89
  }>;
@@ -99,14 +121,14 @@ export declare function emptyGrepPatternRejection(pattern: string | undefined, g
99
121
  export declare function mergeCursorMcpToolCallArgs(streamed: Record<string, unknown> | undefined, completion: Record<string, unknown> | undefined): Record<string, unknown>;
100
122
  /**
101
123
  * Synthesize a completed `toolCall` content block for a Cursor exec-channel
102
- * native tool (`shell`, `read`, `write`, `grep`, `ls`, `delete`, `diagnostics`).
124
+ * native tool (`shell`, `read`, `write`, `grep`, `ls`, `delete`, `diagnostics`)
125
+ * or for an MCP exec frame whose corresponding interaction block is absent.
103
126
  *
104
127
  * Args arrive complete on the exec message, so the block opens and closes in
105
128
  * one step — no partial-JSON streaming path. Without this the persisted
106
129
  * assistant message carries only text/thinking blocks, and on replay the
107
130
  * following `toolResult` messages have no matching `toolCall.id` in
108
- * `renderSessionContext`, so they render as header-less `⎿` lines beneath the
109
- * last text block instead of proper tool components (issue #4348).
131
+ * `renderSessionContext`, so they render beneath the final answer or disappear.
110
132
  *
111
133
  * The block is stamped with {@link kCursorExecResolved} so the shared
112
134
  * `agent-loop.ts` execution pass skips it — Cursor's server-driven exec
@@ -119,6 +141,7 @@ export declare function mergeCursorMcpToolCallArgs(streamed: Record<string, unkn
119
141
  export declare function synthesizeCursorExecToolCall(output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, toolCallId: string, toolName: string, args: Record<string, unknown>): void;
120
142
  /** Exported for tests: drives one Cursor interaction update through the streaming state machine. */
121
143
  export declare function processInteractionUpdate(update: any, output: AssistantMessage, stream: AssistantMessageEventStream, state: BlockState, usageState: UsageState): void;
144
+ export declare function buildMcpToolDefinitions(tools: Tool[] | undefined): McpToolDefinition[];
122
145
  /**
123
146
  * Build `ConversationStateStructure.rootPromptMessagesJson` blob IDs for the
124
147
  * system prompt plus prior conversation history, as JSON blobs matching
@@ -96,10 +96,11 @@ export interface CodexLiteShapedBody {
96
96
  * rewrite removes top-level `tools`, a forced hosted-tool choice (e.g.
97
97
  * `{ type: "web_search" }`) would leave the backend unable to validate the
98
98
  * choice against a tools collection and it rejects the request with HTTP 400
99
- * (#5771). Such choices must fall back to `"auto"`; explicit string constraints
100
- * such as `"none"` and `"required"` remain valid. Shared by normal turns and
101
- * both remote-compaction paths codex-rs routes `/responses/compact` through
102
- * the same builder.
99
+ * (#5771). Native computer and named-function choices preserve exact forcing
100
+ * by isolating the selected declaration and using `"required"`; other hosted
101
+ * choices fall back to `"auto"`. Explicit string constraints such as `"none"`
102
+ * and `"required"` remain valid. Shared by normal turns and both remote-compaction
103
+ * paths — codex-rs routes `/responses/compact` through the same builder.
103
104
  */
104
105
  export declare function applyCodexResponsesLiteShape(body: CodexLiteShapedBody): void;
105
106
  export declare function transformRequestBody(body: RequestBody, model: Model<"openai-codex-responses">, options?: CodexRequestOptions, prompt?: {
@@ -182,6 +182,9 @@ declare function convertMessages(model: Model<"openai-codex-responses">, context
182
182
  /** @internal Exported for tests. */
183
183
  export { convertMessages as convertCodexResponsesMessages };
184
184
  type CodexToolPayload = {
185
+ type: "computer";
186
+ name?: never;
187
+ } | {
185
188
  type: "function";
186
189
  name: string;
187
190
  description: string;
@@ -1,4 +1,15 @@
1
1
  import type { OAuthController, OAuthLoginCallbacks } from "./oauth/types.js";
2
+ /**
3
+ * Log in to the QwenCloud Token Plan provider.
4
+ *
5
+ * The Token Plan ships as two regionally separate products with
6
+ * non-interchangeable keys — International (Singapore) and China (Beijing) —
7
+ * so login first selects the region (or a custom base URL) before pasting the
8
+ * key, mirroring {@link loginAlibabaCodingPlan}. The chosen region is validated
9
+ * against its own `/models` endpoint and, when it diverges from the default
10
+ * international endpoint, stored in the credential so inference and discovery
11
+ * both target it (#6682).
12
+ */
2
13
  export declare function loginAlibabaTokenPlan(options: OAuthController): Promise<string>;
3
14
  export declare const alibabaTokenPlanProvider: {
4
15
  readonly id: "alibaba-token-plan";
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Absolute lifetime of an Anthropic OAuth grant family, anchored at the
3
+ * interactive login. Refresh-token rotation does NOT extend it: ~30 days
4
+ * after authorization the token endpoint returns
5
+ * `invalid_grant: "Refresh token expired"` for the latest rotated token and
6
+ * only a fresh interactive login recovers the account. Observed against
7
+ * production (grants authorized 2026-06-20/06-25 died 30d later to the hour
8
+ * despite healthy 8h rotations); matches Claude Code's documented monthly
9
+ * re-login. Consumers use this to warn before the deadline — it is a display
10
+ * heuristic, not a wire contract.
11
+ */
12
+ export declare const ANTHROPIC_OAUTH_GRANT_TTL_MS: number;
@@ -4,6 +4,7 @@
4
4
  import type { FetchImpl } from "../../types.js";
5
5
  import { OAuthCallbackFlow } from "./callback-server.js";
6
6
  import type { OAuthController, OAuthCredentials } from "./types.js";
7
+ export { ANTHROPIC_OAUTH_GRANT_TTL_MS } from "./anthropic-constants.js";
7
8
  export declare class AnthropicOAuthFlow extends OAuthCallbackFlow {
8
9
  #private;
9
10
  constructor(ctrl: OAuthController);
@@ -1,4 +1,5 @@
1
1
  import type { OAuthCredentials, OAuthProvider, OAuthProviderId, OAuthProviderInfo, OAuthProviderInterface } from "./types.js";
2
+ export * from "./anthropic.js";
2
3
  export * from "./device-code.js";
3
4
  export type * from "./types.js";
4
5
  /**
@@ -18,6 +18,14 @@ export type OAuthCredentials = {
18
18
  orgId?: string;
19
19
  /** Human-readable organization name for display (may embed the email). */
20
20
  orgName?: string;
21
+ /**
22
+ * Epoch ms of the interactive login that minted this grant. Set by
23
+ * `AuthStorage.login`; token refreshes preserve it. Providers with an
24
+ * absolute grant lifetime (Anthropic expires the whole refresh-token
25
+ * family ~30 days after authorization regardless of rotation) use it to
26
+ * surface re-login deadlines before the grant dies.
27
+ */
28
+ authorizedAt?: number;
21
29
  };
22
30
  export type OAuthProvider = OAuthProviderUnion;
23
31
  export type OAuthProviderId = OAuthProvider | (string & {});