@code-yeongyu/senpi 2026.10.18 → 2026.10.19

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 (190) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/dist/bundle/chunks/{anthropic-messages-DDBWM4JZ.js → anthropic-messages-EMTBHJMM.js} +2 -2
  3. package/dist/bundle/chunks/{args-2MYPVFPH.js → args-LWR7VFKS.js} +1 -1
  4. package/dist/bundle/chunks/{azure-openai-responses-3UG5TQU7.js → azure-openai-responses-SNWKFDXK.js} +1 -1
  5. package/dist/bundle/chunks/chatgpt-subscription.js +1 -1
  6. package/dist/bundle/chunks/{chunk-SOF44GFZ.js → chunk-35M5MVUZ.js} +149 -145
  7. package/dist/bundle/chunks/chunk-3DNYAO3P.js +2 -0
  8. package/dist/bundle/chunks/{chunk-T2MHSEQ2.js → chunk-5AOKDC6G.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-MYXNQ7YZ.js → chunk-7QPFFAJT.js} +3 -3
  10. package/dist/bundle/chunks/{chunk-R36U2DJC.js → chunk-AUZXSLZN.js} +1 -1
  11. package/dist/bundle/chunks/{chunk-PMTG4YNS.js → chunk-BOCN744P.js} +1 -1
  12. package/dist/bundle/chunks/{chunk-XTAWER7O.js → chunk-CICJOV6I.js} +1 -1
  13. package/dist/bundle/chunks/{chunk-4UCLDXEF.js → chunk-FM7NDZR5.js} +1 -1
  14. package/dist/bundle/chunks/{chunk-TBRWPNA4.js → chunk-FSENE7JK.js} +2 -2
  15. package/dist/bundle/chunks/{chunk-TUD7XRIS.js → chunk-G7LLAQXI.js} +3 -3
  16. package/dist/bundle/chunks/{chunk-BGD3MZ47.js → chunk-GOKAWZUO.js} +1 -1
  17. package/dist/bundle/chunks/{chunk-XC6UG5SI.js → chunk-IM6ZWYTW.js} +2 -2
  18. package/dist/bundle/chunks/{chunk-KHATAB7P.js → chunk-KM4FMQII.js} +1 -1
  19. package/dist/bundle/chunks/chunk-LAWS3FYQ.js +11 -0
  20. package/dist/bundle/chunks/{chunk-W6YQYGE2.js → chunk-MVNVDGYF.js} +1 -1
  21. package/dist/bundle/chunks/{chunk-W4BO6S7F.js → chunk-PMGEQYEP.js} +2 -2
  22. package/dist/bundle/chunks/chunk-QGJLZ6BI.js +2 -0
  23. package/dist/bundle/chunks/{chunk-5RQNCWS2.js → chunk-QJ6MUFXY.js} +1 -1
  24. package/dist/bundle/chunks/{chunk-57Q7Q627.js → chunk-QRGAMVEE.js} +1 -1
  25. package/dist/bundle/chunks/{chunk-W2257FYP.js → chunk-QXEWAKXA.js} +1 -1
  26. package/dist/bundle/chunks/{chunk-UUMKAA7S.js → chunk-RL5QMGQD.js} +9 -9
  27. package/dist/bundle/chunks/{chunk-D2UQYZVK.js → chunk-SHKXJWK3.js} +1 -1
  28. package/dist/bundle/chunks/chunk-T36LJ6MW.js +3 -0
  29. package/dist/bundle/chunks/{chunk-S4V72F76.js → chunk-VCD7FD64.js} +1 -1
  30. package/dist/bundle/chunks/{chunk-MYPTZCPF.js → chunk-VEKNDMUA.js} +1 -1
  31. package/dist/bundle/chunks/{chunk-IRBAGET4.js → chunk-VPV7ADF3.js} +4 -4
  32. package/dist/bundle/chunks/{chunk-6DDAEDY7.js → chunk-WEHIEACU.js} +1 -1
  33. package/dist/bundle/chunks/{chunk-C2HSPFXN.js → chunk-WQLMQGOW.js} +1 -1
  34. package/dist/bundle/chunks/{chunk-QZIKKUUP.js → chunk-X6TWPBI6.js} +2 -2
  35. package/dist/bundle/chunks/{chunk-VYWODOAM.js → chunk-YUMRZIKJ.js} +6 -6
  36. package/dist/bundle/chunks/chunk-ZPSJQHDK.js +395 -0
  37. package/dist/bundle/chunks/{cli-main-MVVPTGMS.js → cli-main-EABMHWFV.js} +1 -1
  38. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-H4ZPUBX4.js → cloudflare-workers-ai-system-one-PF7FU63D.js} +1 -1
  39. package/dist/bundle/chunks/{google-generative-ai-YLAYNQLO.js → google-generative-ai-UFCHYLRE.js} +1 -1
  40. package/dist/bundle/chunks/{google-vertex-THFXM5R2.js → google-vertex-2FWDY7XX.js} +1 -1
  41. package/dist/bundle/chunks/{help-fast-path-H4ERPCR5.js → help-fast-path-BYWJN6YX.js} +1 -1
  42. package/dist/bundle/chunks/{host-command-2CMRIKC3.js → host-command-5T34JLBP.js} +1 -1
  43. package/dist/bundle/chunks/{host-lifecycle-6Y2EKT4U.js → host-lifecycle-GSRIQP6Z.js} +1 -1
  44. package/dist/bundle/chunks/host-lifecycle.js +5 -5
  45. package/dist/bundle/chunks/{host-session-command-MVHY4TKA.js → host-session-command-LF6JODPX.js} +1 -1
  46. package/dist/bundle/chunks/{interactive-mode-2ZQ43N73.js → interactive-mode-GMWHBIUC.js} +1 -1
  47. package/dist/bundle/chunks/{list-tips-AWDAUHF2.js → list-tips-VSWED3PT.js} +1 -1
  48. package/dist/bundle/chunks/{mistral-conversations-GUGTVRCL.js → mistral-conversations-QHZLDTSP.js} +1 -1
  49. package/dist/bundle/chunks/{multi-session-host-NO4PIOVG.js → multi-session-host-3J7STKGD.js} +6 -6
  50. package/dist/bundle/chunks/openai-codex-responses-7YOBPZ2N.js +10 -0
  51. package/dist/bundle/chunks/{openai-completions-I5N2UIEK.js → openai-completions-HSZPYXEA.js} +1 -1
  52. package/dist/bundle/chunks/{openai-responses-SGLUJVZW.js → openai-responses-7W2KF3W7.js} +1 -1
  53. package/dist/bundle/chunks/openrouter.js +1 -1
  54. package/dist/bundle/chunks/{package-manager-cli-HTZQXYNI.js → package-manager-cli-LWQZSWKL.js} +1 -1
  55. package/dist/bundle/chunks/radius.js +1 -1
  56. package/dist/bundle/chunks/{rotation-stream-TOOIK43C.js → rotation-stream-QRRWL73X.js} +1 -1
  57. package/dist/bundle/chunks/{rpc-mode-6ZWGPICX.js → rpc-mode-6LSWRKXR.js} +1 -1
  58. package/dist/bundle/chunks/{schedule-command-2YW4B655.js → schedule-command-7CXMW4WK.js} +1 -1
  59. package/dist/bundle/chunks/{session-control-endpoint-XC2LPLCD.js → session-control-endpoint-E265WAIQ.js} +1 -1
  60. package/dist/bundle/chunks/{session-picker-7W7GIRJT.js → session-picker-XRB3LP5W.js} +1 -1
  61. package/dist/bundle/chunks/session-worker.js +315 -310
  62. package/dist/bundle/chunks/{typesafe-system-one-VBWWTJTF.js → typesafe-system-one-YA75435G.js} +1 -1
  63. package/dist/bundle/cli.js +2 -2
  64. package/dist/bundle/index.js +1 -1
  65. package/dist/bundle/rpc-entry.js +1 -1
  66. package/dist/bundle/runtime-manifest.json +1 -1
  67. package/dist/core/agent-run-continuation.d.ts +24 -0
  68. package/dist/core/agent-run-continuation.js +57 -0
  69. package/dist/core/agent-session.d.ts +38 -1
  70. package/dist/core/agent-session.js +122 -13
  71. package/dist/core/extension-handler-stall-report.d.ts +11 -0
  72. package/dist/core/extension-handler-stall-report.js +26 -0
  73. package/dist/core/extensions/builtin/anthropic-subscription/account-command.js +1 -0
  74. package/dist/core/extensions/builtin/anthropic-subscription/affinity.d.ts +13 -1
  75. package/dist/core/extensions/builtin/anthropic-subscription/affinity.js +15 -4
  76. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +2 -0
  77. package/dist/core/extensions/builtin/anthropic-subscription/executable.d.ts +5 -0
  78. package/dist/core/extensions/builtin/anthropic-subscription/executable.js +11 -1
  79. package/dist/core/extensions/builtin/anthropic-subscription/failover.d.ts +2 -0
  80. package/dist/core/extensions/builtin/anthropic-subscription/failover.js +3 -2
  81. package/dist/core/extensions/builtin/anthropic-subscription/guidance.d.ts +8 -0
  82. package/dist/core/extensions/builtin/anthropic-subscription/guidance.js +29 -0
  83. package/dist/core/extensions/builtin/anthropic-subscription/settings.d.ts +2 -0
  84. package/dist/core/extensions/builtin/anthropic-subscription/settings.js +4 -0
  85. package/dist/core/extensions/builtin/anthropic-subscription/stream-guidance.js +4 -2
  86. package/dist/core/extensions/handler-budget.d.ts +84 -0
  87. package/dist/core/extensions/handler-budget.js +136 -0
  88. package/dist/core/extensions/runner.d.ts +23 -0
  89. package/dist/core/extensions/runner.js +184 -99
  90. package/dist/core/footer-data-provider.js +2 -1
  91. package/dist/core/keybindings.d.ts +1 -1
  92. package/dist/core/keybindings.js +3 -2
  93. package/dist/core/model-resolver.js +1 -1
  94. package/dist/core/pending-input-journal.d.ts +6 -1
  95. package/dist/core/pending-input-journal.js +90 -34
  96. package/dist/core/pending-input-public.d.ts +11 -0
  97. package/dist/core/pending-input-public.js +10 -0
  98. package/dist/core/pending-input-session-port.d.ts +15 -0
  99. package/dist/core/pending-input-session-port.js +93 -0
  100. package/dist/core/pending-input-session.d.ts +108 -0
  101. package/dist/core/pending-input-session.js +220 -0
  102. package/dist/core/pending-input-store.d.ts +32 -1
  103. package/dist/core/pending-input-store.js +74 -26
  104. package/dist/core/pending-input-types.d.ts +25 -4
  105. package/dist/core/pending-input.js +36 -18
  106. package/dist/core/session-entry-file.js +1 -0
  107. package/dist/core/session-log.js +1 -1
  108. package/dist/core/session-manager.d.ts +1 -0
  109. package/dist/core/session-manager.js +5 -1
  110. package/dist/core/session-move/bundle.d.ts +39 -0
  111. package/dist/core/session-move/bundle.js +108 -0
  112. package/dist/core/session-move/export.d.ts +18 -0
  113. package/dist/core/session-move/export.js +83 -0
  114. package/dist/core/session-move/finish.d.ts +24 -0
  115. package/dist/core/session-move/finish.js +81 -0
  116. package/dist/core/session-move/import.d.ts +32 -0
  117. package/dist/core/session-move/import.js +124 -0
  118. package/dist/core/session-move/move-ledger.d.ts +47 -0
  119. package/dist/core/session-move/move-ledger.js +151 -0
  120. package/dist/core/session-move/ownership.d.ts +61 -0
  121. package/dist/core/session-move/ownership.js +89 -0
  122. package/dist/core/session-move/requeue.d.ts +8 -0
  123. package/dist/core/session-move/requeue.js +18 -0
  124. package/dist/core/session-move/write-guard.d.ts +22 -0
  125. package/dist/core/session-move/write-guard.js +44 -0
  126. package/dist/core/settings-manager.d.ts +13 -0
  127. package/dist/core/settings-manager.js +17 -0
  128. package/dist/core/turn-deadline.d.ts +2 -0
  129. package/dist/core/turn-deadline.js +10 -3
  130. package/dist/modes/interactive/components/ask-user-async-widget.d.ts +7 -0
  131. package/dist/modes/interactive/components/ask-user-async-widget.js +20 -6
  132. package/dist/modes/rpc/connection-handler.js +3 -0
  133. package/dist/modes/rpc/effect-commands.js +3 -4
  134. package/dist/modes/rpc/host-decision.d.ts +1 -1
  135. package/dist/modes/rpc/host-decision.js +2 -0
  136. package/dist/modes/rpc/host-ensure-liveness.d.ts +8 -0
  137. package/dist/modes/rpc/host-ensure-liveness.js +18 -1
  138. package/dist/modes/rpc/host-ensure-types.d.ts +2 -0
  139. package/dist/modes/rpc/host-ensure.js +8 -2
  140. package/dist/modes/rpc/host-lifecycle-proxy.d.ts +9 -2
  141. package/dist/modes/rpc/host-lifecycle-proxy.js +36 -8
  142. package/dist/modes/rpc/host-observe-request.js +1 -0
  143. package/dist/modes/rpc/multi-session-host.js +3 -33
  144. package/dist/modes/rpc/rpc-client.d.ts +10 -0
  145. package/dist/modes/rpc/rpc-client.js +11 -0
  146. package/dist/modes/rpc/rpc-input-validation.js +8 -0
  147. package/dist/modes/rpc/rpc-types.d.ts +25 -1
  148. package/dist/modes/rpc/session-command-activity.js +1 -0
  149. package/dist/modes/rpc/session-control-actions-data.js +17 -8
  150. package/dist/modes/rpc/session-held.js +1 -0
  151. package/dist/modes/rpc/session-release.js +16 -1
  152. package/dist/modes/rpc/socket-ownership.d.ts +17 -0
  153. package/dist/modes/rpc/socket-ownership.js +52 -0
  154. package/dist/modes/rpc/tool-child/admission.d.ts +42 -0
  155. package/dist/modes/rpc/tool-child/admission.js +81 -0
  156. package/dist/modes/rpc/tool-child/admission.test.d.ts +2 -0
  157. package/dist/modes/rpc/tool-child/admission.test.js +166 -0
  158. package/dist/modes/rpc/tool-child/codec.d.ts +21 -0
  159. package/dist/modes/rpc/tool-child/codec.js +137 -0
  160. package/dist/modes/rpc/tool-child/codec.test.d.ts +2 -0
  161. package/dist/modes/rpc/tool-child/codec.test.js +234 -0
  162. package/dist/modes/rpc/tool-child/context.d.ts +42 -0
  163. package/dist/modes/rpc/tool-child/context.js +23 -0
  164. package/dist/modes/rpc/tool-child/flow-control.d.ts +24 -0
  165. package/dist/modes/rpc/tool-child/flow-control.js +80 -0
  166. package/dist/modes/rpc/tool-child/payload-ref-paths.d.ts +9 -0
  167. package/dist/modes/rpc/tool-child/payload-ref-paths.js +45 -0
  168. package/dist/modes/rpc/tool-child/payload-ref.d.ts +58 -0
  169. package/dist/modes/rpc/tool-child/payload-ref.js +166 -0
  170. package/dist/modes/rpc/tool-child/payload-ref.test.d.ts +2 -0
  171. package/dist/modes/rpc/tool-child/payload-ref.test.js +254 -0
  172. package/dist/modes/rpc/tool-child/payload-wire.d.ts +13 -0
  173. package/dist/modes/rpc/tool-child/payload-wire.js +22 -0
  174. package/dist/modes/rpc/tool-child/protocol-guard.d.ts +9 -0
  175. package/dist/modes/rpc/tool-child/protocol-guard.js +128 -0
  176. package/dist/modes/rpc/tool-child/protocol.d.ts +192 -0
  177. package/dist/modes/rpc/tool-child/protocol.js +54 -0
  178. package/dist/utils/wsl.d.ts +2 -2
  179. package/dist/utils/wsl.js +4 -4
  180. package/docs/providers.md +2 -0
  181. package/docs/rpc.md +20 -2
  182. package/docs/settings.md +1 -0
  183. package/docs/terminal-setup.md +2 -0
  184. package/package.json +10 -6
  185. package/dist/bundle/chunks/chunk-COJK5FDH.js +0 -2
  186. package/dist/bundle/chunks/chunk-ES3GHZE5.js +0 -395
  187. package/dist/bundle/chunks/chunk-IDXW4FAO.js +0 -2
  188. package/dist/bundle/chunks/chunk-PFNBY43W.js +0 -2
  189. package/dist/bundle/chunks/chunk-SA2ZLWBD.js +0 -11
  190. package/dist/bundle/chunks/openai-codex-responses-5LRKZCBW.js +0 -10
@@ -1,3 +1,3 @@
1
- /** Windows Subsystem for Linux, where Windows executables are reachable through interop. */
2
- export declare function isWSL(env?: NodeJS.ProcessEnv): boolean;
1
+ /** Windows Subsystem for Linux, where Windows executables are reachable through interop. Linux-only by contract: callers on other platforms must guard with a platform check (as keybindings and the footer provider do), because `WSLENV` can also be set on native Windows. */
2
+ export declare function isWSL(env?: NodeJS.ProcessEnv, procVersionPath?: string): boolean;
3
3
  //# sourceMappingURL=wsl.d.ts.map
package/dist/utils/wsl.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { readFileSync } from "node:fs";
2
- /** Windows Subsystem for Linux, where Windows executables are reachable through interop. */
3
- export function isWSL(env = process.env) {
4
- if (env.WSL_DISTRO_NAME || env.WSLENV) {
2
+ /** Windows Subsystem for Linux, where Windows executables are reachable through interop. Linux-only by contract: callers on other platforms must guard with a platform check (as keybindings and the footer provider do), because `WSLENV` can also be set on native Windows. */
3
+ export function isWSL(env = process.env, procVersionPath = "/proc/version") {
4
+ if (env.WSL_DISTRO_NAME || env.WSL_INTEROP || env.WSLENV) {
5
5
  return true;
6
6
  }
7
7
  try {
8
- const release = readFileSync("/proc/version", "utf-8");
8
+ const release = readFileSync(procVersionPath, "utf-8");
9
9
  return /microsoft|wsl/i.test(release);
10
10
  }
11
11
  catch {
package/docs/providers.md CHANGED
@@ -137,6 +137,7 @@ The `anthropic-subscription` provider routes LLM calls through the official [Cla
137
137
  - Run `/login anthropic-subscription` to sign in with your Claude Pro/Max subscription (PKCE, same OAuth client as the Claude Code CLI). An existing Anthropic OAuth credential is offered as an import.
138
138
  - Multiple accounts: each `/login anthropic-subscription` adds another named account. `CLAUDE_CODE_OAUTH_TOKEN` (and `_2`..`_N`) are honored as read-only env accounts. `/claude-account` lists, adds, removes, and pins accounts; `--claude-account <name>` pins one for the session; `anthropicSubscriptionProvider.pinnedAccount` pins one in settings.
139
139
  - Session affinity: one senpi session sticks to one account (rendezvous hashing), which keeps Anthropic's prompt cache warm - accounts never rotate mid-session except on automatic failover. Rate limits and auth errors block the account (with cooldown) and retry on the next account, before any visible output; once output has started, the error surfaces instead of replaying.
140
+ - To stop instead of spending another account's allowance, set `anthropicSubscriptionProvider.accountFallback: false`. On managed lanes (`oauth-slots` and `config-dir`), only the explicitly pinned account, otherwise the resident session's account, otherwise the deterministic affinity winner may serve the request. A blocked selected account is not skipped on later requests. Same-account transient retries and credential refresh still work; an explicit account pin can select a different account. The default is `true`. This is independent of `retry.modelFallback` / `--no-model-fallback`; disable model fallback too if you do not want a different model or provider to serve the failed turn. The ambient lane has no managed-account fallback.
140
141
  - Default lane: **ambient** - with no `tokenInjection` setting the provider inherits the environment like the upstream extension (Claude Code CLI login or `ANTHROPIC_API_KEY`). Managed lanes (`oauth-slots`, `config-dir`) are opt-in via one settings line until the live subscription spike proves a managed default.
141
142
  - Ambient lane is explicit opt-in: a Claude Code CLI login on the host is not consent to spend that subscription, so the ambient lane is gated by `anthropicSubscriptionProvider.enabled` (default `false`, env override `SENPI_CLAUDE_SDK_OAUTH_ENABLED`). Before this gate existed, a logged-in Claude Code CLI made the provider available with no senpi-side action. An explicit senpi-side login is itself an opt-in: stored OAuth accounts in `auth.json` and `CLAUDE_CODE_OAUTH_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN_<n>` env accounts keep the provider available with the flag unset. Only the host-CLI-derived ambient lane requires the flag.
142
143
  - Settings (`anthropicSubscriptionProvider`):
@@ -154,6 +155,7 @@ The `anthropic-subscription` provider routes LLM calls through the official [Cla
154
155
  | `SENPI_CLAUDE_SDK_OAUTH_TOKEN_INJECTION` | `oauth-slots` \| `config-dir` \| `ambient` |
155
156
  | `SENPI_CLAUDE_SDK_OAUTH_SETTING_SOURCES` | Overrides `settingSources` |
156
157
  | `SENPI_CLAUDE_SDK_OAUTH_PINNED_ACCOUNT` | Overrides `pinnedAccount` |
158
+ | `SENPI_CLAUDE_SDK_OAUTH_ACCOUNT_FALLBACK` | `0` / `false` disables managed-account fallback; `1` / `true` enables it |
157
159
  | `SENPI_CLAUDE_SDK_OAUTH_COMPACTION_OWNER` | Overrides `compactionOwner` (`senpi` \| `sdk`) |
158
160
 
159
161
  Every `SENPI_*` variable is stripped from the Claude Code subprocess environment on all three lanes; other inherited variables are preserved.
package/docs/rpc.md CHANGED
@@ -466,6 +466,10 @@ are what this decision encodes; the other two are what a client must not undo el
466
466
  that window, while its process is alive, the ensure fails with `HostEnsureRefusedError { reason: "host_stalled" }`
467
467
  and sends no signal, starts no generation beside it and leaves its registration untouched - a stalled host is still
468
468
  serving its sessions.
469
+ When no registration names the endpoint at all, yet something still accepts connections at the public path and did
470
+ not answer `get_protocol_info`, the ensure fails with `HostEnsureRefusedError { reason: "endpoint_occupied" }` (exit 3)
471
+ and leaves that process and its entry alone: a start there could only lose the bind and exit without a reason. An
472
+ entry nobody listens behind is still free and is replaced by the start.
469
473
  - **I2 - compatibility is protocol version + capabilities, never semver equality.** An ordinal that cannot be
470
474
  compared (a build without git metadata, a host that reports none) is EQUAL, and since a handoff requires
471
475
  STRICTLY greater, such a pair attaches instead of upgrading.
@@ -1832,6 +1836,8 @@ no longer lists it and the handle answers `unknown_session`.
1832
1836
  `session_released` entry on disk or in memory, and a later release can be retried. A teardown that fails
1833
1837
  after it claimed the session leaves it closing.
1834
1838
 
1839
+ A release with `reason: "move"` (and a `moveId`) seals the session for a move to another device: its `session_released` entry records `move_id`, `target` and `session_id`, and from then on any write to the file is refused with `session_moved_away` until that same move is reclaimed. A later `takeover` release, or any ownership entry for another move, is refused too. A `move` without `moveId` is refused with `invalid_release_reason`.
1840
+
1835
1841
  ### Host self-observation (event-loop stalls and memory pressure)
1836
1842
 
1837
1843
  Every in-process session shares the host's event loop, so a session that blocks it freezes every other session and the
@@ -2050,7 +2056,7 @@ names a valid preset.
2050
2056
  | `close_session` | `sessionId` | `{}` | Refused with `unknown_session` when the requesting connection never attached to that handle (a close releases the CALLER's attachment, and `list_sessions` publishes every handle). Otherwise aborts active work, awaits agent idle + settled persistence for up to the host grace window (default 10s), then quarantines any worker that has not exited without releasing its path reservation; its response is the LAST record tagged with that handle for the first closer — no events after (test-pinned). An admitted concurrent close joins the same teardown and receives its own successful response; output saturation rejects admission with the bounded close-overflow/resync notice described above. |
2051
2057
  | `list_sessions` | `include_workers?` (default false) | `{ sessions: [{ sessionId, durableSessionId, sessionPath, cwd, name, status, attachments, kind, context? }] }` | Includes `opening`/`closing` entries. Internally quarantined workers remain externally `closing` until exit. `attachments` is the session's live client attachment count; `0` on an `open` row is a retained session with no client attached. Every row carries `kind`. Rows with `kind: "worker"` are omitted unless `include_workers: true`, and `context` is published ONLY on that listing — a default listing carries no `context` at all. |
2052
2058
  | `detach_connection` | - | `{ deferred: string[] }` | Multi-session hosts that advertise `detach_connection`. Meant as the connection's last request: the host releases every session attachment this connection holds and answers only after that, so another connection's next `list_sessions` no longer counts it. `deferred` names the sessions that still count it (see `#### detach_connection`). Closing the socket afterwards releases nothing more. `senpi host session` sends it before closing, bounded at 5 s; on a host without the capability, or after a request timed out or the connection was lost, it relies on the release at socket close. |
2053
- | `release_session` | `sessionId`, `reason: "takeover"`, `interrupt?`, `force?` | `{ released: true, session_path, attachments }` | Hands the session to a runtime outside this host. See "Handing a session over (`release_session`)" below. |
2059
+ | `release_session` | `sessionId`, `reason: "takeover" \| "move"`, `moveId?` (required for `move`), `target?`, `interrupt?`, `force?` | `{ released: true, session_path, attachments }` | Hands the session to a runtime outside this host. See "Handing a session over (`release_session`)" below. |
2054
2060
  | `wake` | `sessionId`, `delivery_ids?` | `{ admitted: [{ delivery_id, kind }] }` | Runs the session's registered inbox drain once. See "Waking a host session (`wake`)" below. |
2055
2061
  | `warm` | `cwd?`, `kind?`, `context?` (as on `open_session`; `cwd` MUST be absolute) | `{ state: "warmed" \| "already_warm" \| "unsupported" }` | Loads what the next matching `open_session` needs without opening a session; never listed, never an attachment, idempotent per profile. See "Warming a host" above. Advertised as the `warm` capability by in-process hosts only. |
2056
2062
  | every existing command | + `sessionId` (REQUIRED in multi mode) | unchanged | Routed to that session. |
@@ -2528,6 +2534,18 @@ For both commands, `status` is `removed` / `edited`, or `already_delivered` (the
2528
2534
  {"type": "response", "command": "edit_queued_message", "success": true, "data": {"queuedId": "q_4", "status": "conflict", "currentRevision": 2, "reason": "conflict", "action": "read the input's current text and revision from get_state or queue_update, then retry with that expectedRevision if the change still applies"}}
2529
2535
  ```
2530
2536
 
2537
+ #### get_pending_inputs
2538
+
2539
+ What the engine holds for every pending input, and the outcome of each id a client sent, read in one consistent step (senpi#3549). `inputs` lists every input still queued, sending or failed, each as a projection: `clientMessageId`, `state` (`queued`, `edited`, `sending`, `failed`), `text`, `requestedMode`, `revision`, `statusCode`, `statusText`, `actions` and `pendingDecoration`. `knownClientMessageIds` (at most 64 non-empty ids of at most 256 characters) asks for an outcome per id: `admitted` with its projection, including `delivered` and `removed`, or `not_admitted`. Absence from `inputs` is never an outcome; read the id's outcome instead.
2540
+
2541
+ The answer is consistent through `throughCursor`. To follow changes without a gap, subscribe first, apply the snapshot, then apply only `pending_input_update` events whose `cursor` is later. Each event carries every input one committed change touched, under that change's single `cursor`, with the same `lifetimeId`. A different `lifetimeId` means the session reopened: read the snapshot again.
2542
+
2543
+ ```json
2544
+ {"type": "get_pending_inputs", "knownClientMessageIds": ["c-7"]}
2545
+ {"type": "response", "command": "get_pending_inputs", "success": true, "data": {"lifetimeId": "5b0c…", "throughCursor": 4, "inputs": [{"clientMessageId": "c-7", "state": "queued", "text": "also run the tests", "requestedMode": "followUp", "revision": 1, "statusCode": "queued_follow_up", "statusText": "Queued - sent after this turn", "actions": ["edit", "remove", "copy"], "pendingDecoration": true, "supersededVersions": []}], "outcomes": [{"clientMessageId": "c-7", "status": "admitted", "projection": {"clientMessageId": "c-7", "state": "queued"}}]}}
2546
+ {"type": "pending_input_update", "lifetimeId": "5b0c…", "cursor": 5, "changes": [{"clientMessageId": "c-7", "state": "sending"}]}
2547
+ ```
2548
+
2531
2549
  #### clear_queue
2532
2550
 
2533
2551
  Remove queued steering and follow-up messages and return their text.
@@ -2787,7 +2805,7 @@ List all configured models, refreshing credential availability on each request s
2787
2805
  {"type": "get_available_models"}
2788
2806
  ```
2789
2807
 
2790
- Response contains an array of full [Model](#model) objects with supported thinking levels. Each row also carries `supportsAssistantPrefill`: whether the model accepts a request ending with an assistant message, given the session's current thinking level. It is `false` for every model today, so clients continue an edited answer with `continue_from_leaf`:
2808
+ Response contains an array of full [Model](#model) objects with supported thinking levels. A row's `serviceTier` and `upstreamModelId` are the values the session resolves for that model, including ones declared only in `models.json` or by an extension provider, so a `-fast` priority alias names its base model there and a client can tell a child opened on the base model is the alias it asked for. Each row also carries `supportsAssistantPrefill`: whether the model accepts a request ending with an assistant message, given the session's current thinking level. It is `false` for every model today, so clients continue an edited answer with `continue_from_leaf`:
2791
2809
  ```json
2792
2810
  {
2793
2811
  "type": "response",
package/docs/settings.md CHANGED
@@ -415,6 +415,7 @@ When unset, senpi leaves provider payloads unchanged. This setting currently app
415
415
  | Setting | Type | Default | Description |
416
416
  |---------|------|---------|-------------|
417
417
  | `anthropicSubscriptionProvider.enabled` | boolean | `false` | Enable the ambient (host-CLI-derived) lane of `anthropic-subscription`. Env override: `SENPI_CLAUDE_SDK_OAUTH_ENABLED`. Explicit senpi-side logins (stored OAuth accounts in `auth.json`, `CLAUDE_CODE_OAUTH_TOKEN` / `CLAUDE_CODE_OAUTH_TOKEN_<n>` env accounts) keep the provider available with this unset |
418
+ | `anthropicSubscriptionProvider.accountFallback` | boolean | `true` | Allow automatic switching between managed Claude accounts. `false` surfaces failure instead of using another account, including when the selected account is already blocked. Same-account retries and refresh remain enabled. Independent of `retry.modelFallback`. Env override: `SENPI_CLAUDE_SDK_OAUTH_ACCOUNT_FALLBACK` |
418
419
  | `cursorCliOauthProvider.enabled` | boolean | `false` | Enable the `cursor-cli-oauth` fallback lane and automatic native credential bootstrap. Env override: `SENPI_CURSOR_CLI_OAUTH_ENABLED` |
419
420
  | `providers.<id>.maxConcurrency` | number | unlimited | Cap the streaming requests senpi keeps in flight against one provider, keyed by registry provider id (`anthropic`, `openai`, ...). `0`, a negative value, a fraction, or an absent key all mean unlimited |
420
421
 
@@ -95,6 +95,8 @@ If you already have a `config.keys` table, add the entry to it.
95
95
 
96
96
  On WSL, WezTerm may require a visible hardware cursor for IME candidate window positioning. If CJK IME candidates do not follow the text cursor, set `PI_HARDWARE_CURSOR=1` before running senpi or set `showHardwareCursor` to `true` in settings.
97
97
 
98
+ On Windows, inline images need WezTerm 20250208-160106 or newer, which today means a [nightly build](https://wezterm.org/install/windows.html). WezTerm runs the shell through the ConPTY it bundles, and the ConPTY in older builds (including the 20240203 stable release) drops Kitty graphics before WezTerm sees them. On those builds senpi shows the image placeholder with a hint to update instead of leaving a blank gap. Running senpi through `wezterm ssh` also bypasses ConPTY.
99
+
98
100
  ## Alacritty
99
101
 
100
102
  Alacritty usually works out of the box for `Shift+Enter`. On macOS, `Option+Enter` may arrive as plain `Enter`. To use `Option+Enter` for pi follow-up queueing, add to `~/.config/alacritty/alacritty.toml`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi",
3
- "version": "2026.10.18",
3
+ "version": "2026.10.19",
4
4
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "piConfig": {
@@ -29,6 +29,10 @@
29
29
  "types": "./dist/client/index.d.ts",
30
30
  "import": "./dist/client/index.js"
31
31
  },
32
+ "./pending-input": {
33
+ "types": "./dist/core/pending-input-public.d.ts",
34
+ "import": "./dist/core/pending-input-public.js"
35
+ },
32
36
  "./prompt-presets": {
33
37
  "types": "./dist/core/extensions/builtin/prompt-preset/public.d.ts",
34
38
  "import": "./dist/core/extensions/builtin/prompt-preset/public.js"
@@ -60,9 +64,9 @@
60
64
  },
61
65
  "dependencies": {
62
66
  "@earendil-works/chord": "0.99.1",
63
- "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.18",
64
- "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.18",
65
- "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.18",
67
+ "@earendil-works/pi-agent-core": "npm:@code-yeongyu/senpi-agent-core@2026.10.19",
68
+ "@earendil-works/pi-ai": "npm:@code-yeongyu/senpi-ai@2026.10.19",
69
+ "@earendil-works/pi-tui": "npm:@code-yeongyu/senpi-tui@2026.10.19",
66
70
  "@silvia-odwyer/photon-node": "0.3.4",
67
71
  "chalk": "6.0.0",
68
72
  "cross-spawn": "7.0.6",
@@ -81,8 +85,8 @@
81
85
  "yaml": "2.9.1",
82
86
  "@anthropic-ai/claude-agent-sdk": "0.3.296",
83
87
  "@anthropic-ai/sdk": "0.127.0",
84
- "@code-yeongyu/senpi-codemode": "2026.10.18",
85
- "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.18",
88
+ "@code-yeongyu/senpi-codemode": "2026.10.19",
89
+ "@earendil-works/pi-pty": "npm:@code-yeongyu/senpi-pty@2026.10.19",
86
90
  "@modelcontextprotocol/sdk": "1.32.1",
87
91
  "@mozilla/readability": "0.6.0",
88
92
  "linkedom": "0.18.13",
@@ -1,2 +0,0 @@
1
- import { createRequire as __piCreateRequire } from "node:module"; const require = __piCreateRequire(import.meta.url); { const __piWorkerThreads = require("node:worker_threads"); if (typeof __piWorkerThreads.markAsUncloneable !== "function") { __piWorkerThreads.markAsUncloneable = () => {}; } }
2
- import{getProviderEnvValue}from"./chunk-5ROMKDLK.js";import{appendAssistantMessageDiagnostic}from"./chunk-DMI55M2N.js";function isObject(value){return typeof value=="object"&&value!==null}function hasMessagesArray(value){return isObject(value)&&Array.isArray(value.messages)}function isContentBlockParam(value){return isObject(value)&&typeof value.type=="string"}function isToolUseBlock(block){return block.type==="tool_use"&&"id"in block&&typeof block.id=="string"}function isToolResultBlock(block){return block.type==="tool_result"}var SYNTHETIC_OUTPUT="Tool output unavailable (interrupted before result)";function cloneWith(block,patch){return{...block,...patch}}function isMessageWithArrayContent(value,role){if(!isObject(value)||value.role!==role||!Array.isArray(value.content))return!1;for(let block of value.content)if(!isContentBlockParam(block))return!1;return!0}function isUserMessageWithStringContent(value){return isObject(value)&&value.role==="user"&&typeof value.content=="string"}function dedupeAssistantToolUses(message,usedIds){let content=[],expectedIds=[],remapQueues=new Map,changed=!1;for(let block of message.content){if(!isToolUseBlock(block)||block.id.length===0){content.push(block);continue}let finalId=block.id;if(usedIds.has(finalId)){let suffix=2;for(;usedIds.has(`${block.id}__dedup${suffix}`);)suffix+=1;finalId=`${block.id}__dedup${suffix}`,changed=!0}usedIds.add(finalId),expectedIds.push(finalId);let queue=remapQueues.get(block.id);queue===void 0?remapQueues.set(block.id,[finalId]):queue.push(finalId),content.push(finalId===block.id?block:cloneWith(block,{id:finalId}))}return changed?{message:{...message,content},changed:!0,expectedIds,remapQueues}:{message,changed:!1,expectedIds,remapQueues}}function syntheticToolResult(toolUseId){return{type:"tool_result",tool_use_id:toolUseId,content:SYNTHETIC_OUTPUT,is_error:!0}}function sameBlocks(left,right){return left.length===right.length&&left.every((block,index)=>block===right[index])}function repairFollowingUserMessage(message,expectedIds,remapQueues){let expected=new Set(expectedIds),found=new Set,results=[],ordinary=[];for(let block of message.content){if(!isToolResultBlock(block)){ordinary.push(block);continue}let rawId=block.tool_use_id,id=rawId;if(typeof rawId=="string"){let queue=remapQueues.get(rawId);queue!==void 0&&queue.length>0&&(id=queue.shift())}typeof id!="string"||!expected.has(id)||found.has(id)||(found.add(id),results.push(id===rawId?block:cloneWith(block,{tool_use_id:id})))}for(let id of expectedIds)found.has(id)||results.push(syntheticToolResult(id));let content=[...results,...ordinary];return sameBlocks(content,message.content)?{message,changed:!1}:{message:{...message,content},changed:!0}}function removeOrphanResults(message){let content=message.content.filter(block=>!isToolResultBlock(block));return content.length===message.content.length?{message,changed:!1}:content.length===0?{changed:!0}:{message:{...message,content},changed:!0}}function sanitizeAnthropicToolPairs(payload){if(!hasMessagesArray(payload))return payload;let changed=!1,usedToolUseIds=new Set,sanitizedMessages=[];for(let index=0;index<payload.messages.length;index++){let unknownMessage=payload.messages[index];if(isMessageWithArrayContent(unknownMessage,"assistant")){let deduped=dedupeAssistantToolUses(unknownMessage,usedToolUseIds);sanitizedMessages.push(deduped.message),changed||=deduped.changed;let expectedIds=deduped.expectedIds;if(expectedIds.length===0)continue;let nextMessage=payload.messages[index+1];if(isMessageWithArrayContent(nextMessage,"user")){let repaired=repairFollowingUserMessage(nextMessage,expectedIds,deduped.remapQueues);sanitizedMessages.push(repaired.message),changed||=repaired.changed,index++;continue}if(isUserMessageWithStringContent(nextMessage)){sanitizedMessages.push({...nextMessage,content:[...expectedIds.map(syntheticToolResult),{type:"text",text:nextMessage.content}]}),changed=!0,index++;continue}sanitizedMessages.push({role:"user",content:expectedIds.map(syntheticToolResult)}),changed=!0;continue}if(isMessageWithArrayContent(unknownMessage,"user")){let repaired=removeOrphanResults(unknownMessage);repaired.message&&sanitizedMessages.push(repaired.message),changed||=repaired.changed;continue}sanitizedMessages.push(unknownMessage)}return changed?{...payload,messages:sanitizedMessages}:payload}var __rewriteRelativeImportExtension=function(path,preserveJsx){return typeof path=="string"&&/^\.\.?\//.test(path)?path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i,function(m,tsx,d,ext,cm){return tsx?preserveJsx?".jsx":".js":d&&(!ext||!cm)?m:d+ext+"."+cm.toLowerCase()+"js"}):path},_existsSync=null,_homedir=null,_join=null,dynamicImport=specifier=>import(__rewriteRelativeImportExtension(specifier)),NODE_FS_SPECIFIER="node:fs",NODE_OS_SPECIFIER="node:os",NODE_PATH_SPECIFIER="node:path";typeof process<"u"&&(process.versions?.node||process.versions?.bun)&&(dynamicImport(NODE_FS_SPECIFIER).then(m=>{_existsSync=m.existsSync}),dynamicImport(NODE_OS_SPECIFIER).then(m=>{_homedir=m.homedir}),dynamicImport(NODE_PATH_SPECIFIER).then(m=>{_join=m.join}));var ANTHROPIC_AUTH_TOKEN_ENV="ANTHROPIC_AUTH_TOKEN",ANTHROPIC_OAUTH_TOKEN_ENV="ANTHROPIC_OAUTH_TOKEN",ANTHROPIC_API_KEY_ENV="ANTHROPIC_API_KEY",ANTHROPIC_FEDERATION_RULE_ID_ENV="ANTHROPIC_FEDERATION_RULE_ID",ANTHROPIC_ORGANIZATION_ID_ENV="ANTHROPIC_ORGANIZATION_ID",ANTHROPIC_SERVICE_ACCOUNT_ID_ENV="ANTHROPIC_SERVICE_ACCOUNT_ID",ANTHROPIC_IDENTITY_TOKEN_FILE_ENV="ANTHROPIC_IDENTITY_TOKEN_FILE",ANTHROPIC_WORKSPACE_ID_ENV="ANTHROPIC_WORKSPACE_ID",cachedVertexAdcCredentialsExists=null;function hasVertexAdcCredentials(env){let explicitCredentialsPath=env?.GOOGLE_APPLICATION_CREDENTIALS;if(explicitCredentialsPath)return _existsSync?_existsSync(explicitCredentialsPath):!1;if(cachedVertexAdcCredentialsExists===null){if(!_existsSync||!_homedir||!_join)return typeof process<"u"&&(process.versions?.node||process.versions?.bun)||(cachedVertexAdcCredentialsExists=!1),!1;let gacPath=getProviderEnvValue("GOOGLE_APPLICATION_CREDENTIALS",env);gacPath?cachedVertexAdcCredentialsExists=_existsSync(gacPath):cachedVertexAdcCredentialsExists=_existsSync(_join(_homedir(),".config","gcloud","application_default_credentials.json"))}return cachedVertexAdcCredentialsExists}var API_KEY_ENV_VARS={"alibaba-token-plan":"ALIBABA_TOKEN_PLAN_API_KEY","ant-ling":"ANT_LING_API_KEY","qwen-token-plan":"QWEN_TOKEN_PLAN_API_KEY","qwen-token-plan-cn":"QWEN_TOKEN_PLAN_CN_API_KEY","qwen-token-plan-individual":"QWEN_TOKEN_PLAN_API_KEY",openai:"OPENAI_API_KEY",ollama:"OLLAMA_API_KEY","azure-openai-responses":"AZURE_OPENAI_API_KEY",nvidia:"NVIDIA_API_KEY",deepseek:"DEEPSEEK_API_KEY",google:"GEMINI_API_KEY","google-vertex":"GOOGLE_CLOUD_API_KEY",groq:"GROQ_API_KEY",cerebras:"CEREBRAS_API_KEY",venice:"VENICE_API_KEY",xai:"XAI_API_KEY",typesafe:"TYPESAFE_API_KEY",radius:"RADIUS_API_KEY",openrouter:"OPENROUTER_API_KEY","vercel-ai-gateway":"AI_GATEWAY_API_KEY",opengateway:"OPENGATEWAY_API_KEY",zai:"ZAI_API_KEY","zai-coding-cn":"ZAI_CODING_CN_API_KEY",mistral:"MISTRAL_API_KEY",minimax:"MINIMAX_API_KEY","minimax-cn":"MINIMAX_CN_API_KEY",moonshotai:"MOONSHOT_API_KEY","moonshotai-cn":"MOONSHOT_API_KEY",huggingface:"HF_TOKEN",fireworks:"FIREWORKS_API_KEY",together:"TOGETHER_API_KEY",baseten:"BASETEN_API_KEY",bai:"BAI_API_KEY",opencode:"OPENCODE_API_KEY","opencode-go":"OPENCODE_API_KEY","kimi-coding":"KIMI_API_KEY",meta:"META_API_KEY","cloudflare-workers-ai":"CLOUDFLARE_API_KEY","cloudflare-ai-gateway":"CLOUDFLARE_API_KEY",xiaomi:"XIAOMI_API_KEY","xiaomi-token-plan-cn":"XIAOMI_TOKEN_PLAN_CN_API_KEY","xiaomi-token-plan-ams":"XIAOMI_TOKEN_PLAN_AMS_API_KEY","xiaomi-token-plan-sgp":"XIAOMI_TOKEN_PLAN_SGP_API_KEY"},VERTEX_ADC_ENV_NAMES=["GOOGLE_APPLICATION_CREDENTIALS","GOOGLE_CLOUD_PROJECT","GCLOUD_PROJECT","GOOGLE_CLOUD_LOCATION"],BEDROCK_CREDENTIAL_ENV_NAMES=["AWS_PROFILE","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","AWS_BEARER_TOKEN_BEDROCK","AWS_CONTAINER_CREDENTIALS_RELATIVE_URI","AWS_CONTAINER_CREDENTIALS_FULL_URI","AWS_WEB_IDENTITY_TOKEN_FILE"];function apiKeyEnvProviders(){return["github-copilot","anthropic",...Object.keys(API_KEY_ENV_VARS)]}function providerCredentialEnvNames(){let names=new Set([...VERTEX_ADC_ENV_NAMES,...BEDROCK_CREDENTIAL_ENV_NAMES]);for(let provider of apiKeyEnvProviders())for(let name of getApiKeyEnvVars(provider)??[])names.add(name);return[...names]}function getApiKeyEnvVars(provider){if(provider==="github-copilot")return["COPILOT_GITHUB_TOKEN"];if(provider==="anthropic")return[ANTHROPIC_AUTH_TOKEN_ENV,ANTHROPIC_OAUTH_TOKEN_ENV,ANTHROPIC_API_KEY_ENV];let envVar=API_KEY_ENV_VARS[provider];return envVar?[envVar]:void 0}function findEnvKeys(provider,env){let envVars=getApiKeyEnvVars(provider);if(!envVars)return;let found=envVars.filter(envVar=>!!getProviderEnvValue(envVar,env));return found.length>0?found:void 0}function getEnvApiKey(provider,env){let envKeys=findEnvKeys(provider,env);if(envKeys?.[0]){let apiKeyEnv=provider==="anthropic"?envKeys.find(key=>key!==ANTHROPIC_AUTH_TOKEN_ENV):envKeys[0];if(apiKeyEnv)return getProviderEnvValue(apiKeyEnv,env)}if(provider==="google-vertex"){let hasCredentials=hasVertexAdcCredentials(env),hasProject=!!(getProviderEnvValue("GOOGLE_CLOUD_PROJECT",env)||getProviderEnvValue("GCLOUD_PROJECT",env)),hasLocation=!!getProviderEnvValue("GOOGLE_CLOUD_LOCATION",env);if(hasCredentials&&hasProject&&hasLocation)return"<authenticated>"}if(provider==="amazon-bedrock"&&(getProviderEnvValue("AWS_PROFILE",env)||getProviderEnvValue("AWS_ACCESS_KEY_ID",env)&&getProviderEnvValue("AWS_SECRET_ACCESS_KEY",env)||getProviderEnvValue("AWS_BEARER_TOKEN_BEDROCK",env)||getProviderEnvValue("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI",env)||getProviderEnvValue("AWS_CONTAINER_CREDENTIALS_FULL_URI",env)||getProviderEnvValue("AWS_WEB_IDENTITY_TOKEN_FILE",env)))return"<authenticated>"}var SERVER_FALLBACK_ABORTED_DIAGNOSTIC="server_fallback_aborted",BILLING_INCOMPLETE_DIAGNOSTIC="billing_incomplete_after_client_abort";function isRecord(value){return typeof value=="object"&&value!==null&&!Array.isArray(value)}function readModel(value){if(isRecord(value))return typeof value.model=="string"&&value.model.length>0?value.model:void 0}function parseServerFallbackReceipt(block){if(!isRecord(block)||block.type!=="fallback")return;let from=readModel(block.from),to=readModel(block.to);return from!==void 0&&to!==void 0?{from,to}:void 0}function parseStickyFallbackReceipt(usage,requestedModel,servedModel){if(!(!isRecord(usage)||!Array.isArray(usage.iterations)))for(let index=usage.iterations.length-1;index>=0;index--){let entry=usage.iterations[index];if(!isRecord(entry)||entry.type!=="fallback_message")continue;let to=(typeof entry.model=="string"&&entry.model.length>0?entry.model:void 0)??servedModel;return to!==void 0?{from:requestedModel,to}:void 0}}function serverFallbackRefusalExplanation(receipt){return`Server-side fallback (${receipt.from} -> ${receipt.to}) aborted by client policy`}function applyServerFallbackContinuation(message,model,receipt){for(let[index,block]of message.content.entries())block.type==="toolCall"&&(message.content[index]={type:"providerNative",subtype:"discarded_tool_call",raw:block});message.model=receipt.to;let fallback=model.compat?.allowedFallbackModels?.find(candidate=>typeof candidate!="string"&&candidate.provider===model.provider&&candidate.model===receipt.to);return{...model,id:receipt.to,cost:fallback&&typeof fallback!="string"?fallback.cost:model.cost}}function applyServerFallbackAbort(message,receipt){let explanation=serverFallbackRefusalExplanation(receipt);message.content=[],message.stopReason="error",message.stopDetails={type:"refusal",explanation},message.errorMessage=explanation,appendAssistantMessageDiagnostic(message,{type:SERVER_FALLBACK_ABORTED_DIAGNOSTIC,timestamp:Date.now(),details:{from:receipt.from,to:receipt.to}}),appendAssistantMessageDiagnostic(message,{type:BILLING_INCOMPLETE_DIAGNOSTIC,timestamp:Date.now(),details:{reason:"per-attempt usage does not arrive after a client abort"}})}export{sanitizeAnthropicToolPairs,ANTHROPIC_AUTH_TOKEN_ENV,ANTHROPIC_OAUTH_TOKEN_ENV,ANTHROPIC_API_KEY_ENV,ANTHROPIC_FEDERATION_RULE_ID_ENV,ANTHROPIC_ORGANIZATION_ID_ENV,ANTHROPIC_SERVICE_ACCOUNT_ID_ENV,ANTHROPIC_IDENTITY_TOKEN_FILE_ENV,ANTHROPIC_WORKSPACE_ID_ENV,apiKeyEnvProviders,providerCredentialEnvNames,getApiKeyEnvVars,findEnvKeys,getEnvApiKey,SERVER_FALLBACK_ABORTED_DIAGNOSTIC,BILLING_INCOMPLETE_DIAGNOSTIC,parseServerFallbackReceipt,parseStickyFallbackReceipt,serverFallbackRefusalExplanation,applyServerFallbackContinuation,applyServerFallbackAbort};