@bitkyc08/opencodex 2.84.0 → 2.87.0

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 (518) hide show
  1. package/README.md +0 -4
  2. package/bin/ocx.mjs +61 -3
  3. package/gui/dist/assets/App-ZfYbLHkB.js +58 -0
  4. package/gui/dist/assets/App-wT3mXqFz.css +1 -0
  5. package/gui/dist/assets/{Tray-C9TGtwad.js → Tray-DCd-A1az.js} +1 -1
  6. package/gui/dist/assets/index-CfYDMj34.js +94 -0
  7. package/gui/dist/assets/index-DPeB9HpV.css +1 -0
  8. package/gui/dist/assets/quota-summary-ChvmKGOk.js +1 -0
  9. package/gui/dist/index.html +2 -2
  10. package/package.json +1 -2
  11. package/src/adapters/anthropic/beta-allowlist.ts +91 -11
  12. package/src/adapters/anthropic/beta-body-pairs.ts +93 -0
  13. package/src/adapters/anthropic/client-identity.ts +1 -1
  14. package/src/adapters/anthropic/passthrough.ts +16 -3
  15. package/src/adapters/anthropic-progress.ts +84 -0
  16. package/src/adapters/anthropic.ts +108 -21
  17. package/src/adapters/azure.ts +19 -1
  18. package/src/adapters/base.ts +2 -0
  19. package/src/adapters/claude-cli/adapter.ts +111 -23
  20. package/src/adapters/claude-cli/profiles.ts +2 -1
  21. package/src/adapters/claude-cli/stable-replay.ts +108 -0
  22. package/src/adapters/claude-cli/usage-admission.ts +230 -0
  23. package/src/adapters/client-fingerprint.ts +24 -0
  24. package/src/adapters/coding-agent/protocol.ts +1 -1
  25. package/src/adapters/coding-agent/turn.ts +40 -11
  26. package/src/adapters/cursor/arg-codec.ts +17 -5
  27. package/src/adapters/cursor/catalog.ts +8 -0
  28. package/src/adapters/cursor/effort-map.ts +3 -0
  29. package/src/adapters/cursor/freeform-args.ts +75 -0
  30. package/src/adapters/cursor/protobuf-events.ts +36 -23
  31. package/src/adapters/dsh-account.ts +97 -0
  32. package/src/adapters/google-antigravity-wire.ts +22 -1
  33. package/src/adapters/google.ts +37 -10
  34. package/src/adapters/openai-chat/messages.ts +4 -3
  35. package/src/adapters/openai-chat/passthrough.ts +8 -2
  36. package/src/adapters/openai-chat/reasoning-replay.ts +52 -0
  37. package/src/adapters/openai-chat/response-events.ts +59 -1
  38. package/src/adapters/openai-chat.ts +8 -5
  39. package/src/adapters/openai-responses/compaction-search-history.ts +14 -7
  40. package/src/adapters/openai-responses/passthrough.ts +57 -125
  41. package/src/adapters/openai-responses/request-strips.ts +25 -3
  42. package/src/adapters/openai-responses/transport.ts +81 -0
  43. package/src/adapters/qoder/adapter.ts +44 -3
  44. package/src/adapters/registry.ts +5 -0
  45. package/src/adapters/zed.ts +26 -1
  46. package/src/advisor/activity.ts +55 -0
  47. package/src/advisor/budget.ts +62 -0
  48. package/src/advisor/consult.ts +223 -0
  49. package/src/advisor/context.ts +238 -0
  50. package/src/advisor/disclosure.ts +20 -0
  51. package/src/advisor/evidence-tool.ts +21 -0
  52. package/src/advisor/evidence.ts +117 -0
  53. package/src/advisor/runtime.ts +353 -0
  54. package/src/advisor/settings.ts +144 -0
  55. package/src/advisor/state.ts +369 -0
  56. package/src/advisor/synthetic-tool.ts +33 -0
  57. package/src/bridge/errors.ts +24 -1
  58. package/src/bridge/internal.ts +3 -1
  59. package/src/bridge/response-json.ts +18 -3
  60. package/src/bridge/sse.ts +58 -8
  61. package/src/bridge/terminal-refusal.ts +59 -0
  62. package/src/chat/inbound.ts +30 -6
  63. package/src/chat/outbound.ts +5 -0
  64. package/src/claude/desktop-3p-library.ts +72 -7
  65. package/src/claude/desktop-3p.ts +75 -19
  66. package/src/claude/desktop-first-party.ts +1 -1
  67. package/src/claude/desktop-picker-platform.ts +4 -0
  68. package/src/claude/desktop-picker-profile.ts +11 -4
  69. package/src/claude/desktop-picker.ts +15 -8
  70. package/src/claude/desktop-profile.ts +10 -1
  71. package/src/claude/desktop-remote-store-io.ts +5 -3
  72. package/src/claude/desktop-system-proxy.ts +50 -4
  73. package/src/claude/inbound-content-options.ts +11 -0
  74. package/src/claude/inbound.ts +154 -50
  75. package/src/claude/intercept/local-ca.ts +8 -1
  76. package/src/claude/intercept/picker-ca-startup.ts +2 -1
  77. package/src/claude/intercept/picker-runtime.ts +6 -5
  78. package/src/claude/intercept/picker-trust-windows.ts +338 -0
  79. package/src/claude/intercept/picker-trust.ts +19 -0
  80. package/src/claude/intercept/port-policy.ts +30 -0
  81. package/src/claude/intercept/runtime.ts +97 -94
  82. package/src/claude/model-default-settings.ts +154 -0
  83. package/src/claude/model-info.ts +7 -7
  84. package/src/claude/outbound.ts +51 -19
  85. package/src/cli/account-api.ts +1 -0
  86. package/src/cli/account-policy-dto.ts +6 -0
  87. package/src/cli/account-policy.ts +34 -5
  88. package/src/cli/account-preservation.ts +44 -0
  89. package/src/cli/account.ts +8 -2
  90. package/src/cli/advisor.ts +165 -0
  91. package/src/cli/auxiliary-listener-preflight.ts +37 -0
  92. package/src/cli/capabilities-accounts.ts +20 -1
  93. package/src/cli/capabilities-agents-routing.ts +23 -2
  94. package/src/cli/capabilities-base.ts +39 -11
  95. package/src/cli/capabilities-observe-system.ts +63 -3
  96. package/src/cli/capabilities-provider-models.ts +11 -0
  97. package/src/cli/chatgpt-command.ts +35 -14
  98. package/src/cli/claude.ts +46 -6
  99. package/src/cli/codex-cli-update.ts +151 -41
  100. package/src/cli/codex-provider.ts +44 -0
  101. package/src/cli/combo-input.ts +10 -1
  102. package/src/cli/combo-result.ts +1 -1
  103. package/src/cli/combo.ts +2 -1
  104. package/src/cli/cross-home-owner.ts +17 -14
  105. package/src/cli/dispatch.ts +30 -5
  106. package/src/cli/doctor.ts +15 -1
  107. package/src/cli/ensure-desktop-cold-start.ts +382 -0
  108. package/src/cli/export-command.ts +1 -1
  109. package/src/cli/help.ts +4 -2
  110. package/src/cli/index.ts +14 -5
  111. package/src/cli/integration-plan-dto.ts +3 -0
  112. package/src/cli/log-history.ts +172 -0
  113. package/src/cli/models-runtime-subcommands.ts +1 -0
  114. package/src/cli/models-runtime.ts +72 -8
  115. package/src/cli/observe-stream.ts +2 -0
  116. package/src/cli/observe.ts +16 -4
  117. package/src/cli/provider.ts +27 -5
  118. package/src/cli/registry.ts +41 -9
  119. package/src/cli/restart-scope.ts +3 -1
  120. package/src/cli/runtime-api.ts +5 -3
  121. package/src/cli/system-command.ts +40 -8
  122. package/src/cli/uninstall-client-state.ts +32 -0
  123. package/src/cli/usage-compare.ts +309 -0
  124. package/src/cli/usage-model-search.ts +4 -4
  125. package/src/cli/usage-report.ts +2 -2
  126. package/src/cli/v2.ts +1 -1
  127. package/src/client/machine-listener.ts +1 -3
  128. package/src/client/runtime.ts +1 -0
  129. package/src/clients/client-path.ts +51 -0
  130. package/src/clients/config-export/contracts.ts +3 -1
  131. package/src/clients/config-export/model-metadata.ts +3 -2
  132. package/src/clients/config-export/qoder.ts +93 -0
  133. package/src/clients/config-export.ts +35 -48
  134. package/src/clients/gentle-shell-path.ts +87 -0
  135. package/src/codex/account-auto-switch.ts +2 -0
  136. package/src/codex/account-identity.ts +29 -0
  137. package/src/codex/account-label.ts +1 -1
  138. package/src/codex/account-lifecycle.ts +21 -5
  139. package/src/codex/account-preservation.ts +28 -0
  140. package/src/codex/account-use-remaining.ts +23 -0
  141. package/src/codex/app-server-processes.ts +97 -7
  142. package/src/codex/app-server-restart-service.ts +55 -4
  143. package/src/codex/auth-api/account-list.ts +4 -0
  144. package/src/codex/auth-api/account-pause-group.ts +6 -50
  145. package/src/codex/auth-api/account-preservation.ts +57 -0
  146. package/src/codex/auth-api/pool-mode-gate.ts +5 -2
  147. package/src/codex/auth-api/routes.ts +8 -0
  148. package/src/codex/auth-api/use-remaining-route.ts +36 -0
  149. package/src/codex/auth-api.ts +0 -1
  150. package/src/codex/auth-context.ts +81 -7
  151. package/src/codex/autostart-health.ts +4 -0
  152. package/src/codex/catalog/auto-review.ts +20 -3
  153. package/src/codex/catalog/build-entries.ts +19 -2
  154. package/src/codex/catalog/combo-member.ts +14 -5
  155. package/src/codex/catalog/configured-tiers.ts +50 -0
  156. package/src/codex/catalog/effort.ts +4 -1
  157. package/src/codex/catalog/gather-capture.ts +2 -0
  158. package/src/codex/catalog/metadata.ts +11 -9
  159. package/src/codex/catalog/model-hints.ts +42 -19
  160. package/src/codex/catalog/model-visibility.ts +3 -1
  161. package/src/codex/catalog/native-ultrafast.ts +106 -0
  162. package/src/codex/catalog/parsing.ts +35 -86
  163. package/src/codex/catalog/provider-models.ts +40 -34
  164. package/src/codex/catalog/retained-sync.ts +2 -0
  165. package/src/codex/catalog/routed-gather.ts +33 -16
  166. package/src/codex/catalog/vendor-context.ts +82 -0
  167. package/src/codex/cli-install-provenance.ts +105 -17
  168. package/src/codex/cli-update-plan.ts +545 -0
  169. package/src/codex/config-write-lock.ts +63 -4
  170. package/src/codex/convergence.ts +2 -0
  171. package/src/codex/desktop-app/picker-freshness.ts +96 -0
  172. package/src/codex/desktop-app/types.ts +2 -0
  173. package/src/codex/desktop-app/windows.ts +29 -16
  174. package/src/codex/desktop-app-restart.ts +25 -2
  175. package/src/codex/desktop-compatibility/certificate-service.ts +99 -0
  176. package/src/codex/desktop-compatibility/certificate-store.ts +193 -0
  177. package/src/codex/desktop-compatibility/connection-store.ts +87 -0
  178. package/src/codex/desktop-compatibility/installed-build.ts +73 -0
  179. package/src/codex/desktop-compatibility/json-body.ts +29 -0
  180. package/src/codex/desktop-compatibility/native-identity.ts +62 -0
  181. package/src/codex/desktop-compatibility/relay-listener.ts +93 -0
  182. package/src/codex/desktop-compatibility/routing-binding.ts +10 -0
  183. package/src/codex/desktop-compatibility/routing-preflight.ts +69 -0
  184. package/src/codex/desktop-compatibility/runtime-ownership.ts +28 -0
  185. package/src/codex/desktop-compatibility/runtime.ts +182 -0
  186. package/src/codex/desktop-compatibility/service.ts +30 -0
  187. package/src/codex/desktop-compatibility/startup-settings.ts +50 -0
  188. package/src/codex/desktop-compatibility/usage-activation.ts +86 -0
  189. package/src/codex/desktop-compatibility/usage-controlled-fetch.ts +59 -0
  190. package/src/codex/desktop-compatibility/usage-controller.ts +107 -0
  191. package/src/codex/desktop-compatibility/usage-policy.ts +59 -0
  192. package/src/codex/desktop-compatibility/usage-refresh.ts +33 -0
  193. package/src/codex/desktop-compatibility/usage-sse-controller.ts +86 -0
  194. package/src/codex/desktop-compatibility/windows-activation-source.ts +44 -0
  195. package/src/codex/desktop-compatibility/windows-certificate-trust.ts +81 -0
  196. package/src/codex/desktop-compatibility/windows-key-protection.ts +165 -0
  197. package/src/codex/desktop-compatibility/windows-package-command.ts +86 -0
  198. package/src/codex/desktop-compatibility/windows-package-launch.ts +28 -0
  199. package/src/codex/features.ts +85 -45
  200. package/src/codex/history-provider.ts +1 -1
  201. package/src/codex/inject/config-write-section.ts +4 -1
  202. package/src/codex/inject/history-failure.ts +27 -0
  203. package/src/codex/inject/remove.ts +18 -6
  204. package/src/codex/inject/restore.ts +24 -2
  205. package/src/codex/inject/routing-classify.ts +4 -3
  206. package/src/codex/inject.ts +18 -27
  207. package/src/codex/journal.ts +16 -1
  208. package/src/codex/main-account-hard-lock.ts +30 -1
  209. package/src/codex/main-account.ts +99 -15
  210. package/src/codex/native-main-refresh-mode.ts +217 -0
  211. package/src/codex/native-profile-startup.ts +1 -1
  212. package/src/codex/ocx-compaction-history.ts +12 -10
  213. package/src/codex/paths.ts +86 -1
  214. package/src/codex/provider-routing.ts +174 -0
  215. package/src/codex/refresh.ts +2 -2
  216. package/src/codex/routing/health-store.ts +2 -2
  217. package/src/codex/routing/pool-recovery.ts +79 -0
  218. package/src/codex/routing/selection.ts +4 -3
  219. package/src/codex/routing/thread-affinity.ts +1 -1
  220. package/src/codex/shim-diagnostics.ts +6 -0
  221. package/src/codex/shim-overlay.ts +7 -2
  222. package/src/codex/signed-in-identity.ts +43 -0
  223. package/src/codex/subagent-model-fallback.ts +1 -1
  224. package/src/codex/sync.ts +6 -1
  225. package/src/codex/user-identity.ts +16 -5
  226. package/src/codex/windows-local-appdata.ts +84 -0
  227. package/src/combos/failover.ts +6 -1
  228. package/src/combos/index.ts +1 -0
  229. package/src/combos/resolve.ts +46 -10
  230. package/src/combos/types.ts +15 -0
  231. package/src/config/diagnostics.ts +35 -7
  232. package/src/config/live-reconcile.ts +51 -2
  233. package/src/config/load-degrade.ts +23 -1
  234. package/src/config/opencode-wire-upgrade.ts +54 -0
  235. package/src/config/opencode-zen-upgrade.ts +52 -0
  236. package/src/config/owner-registry.ts +22 -17
  237. package/src/config/paths.ts +10 -3
  238. package/src/config/provider-validation.ts +45 -2
  239. package/src/config/schema/config-schema.ts +39 -0
  240. package/src/config/schema/desktop-compatibility.ts +9 -0
  241. package/src/config/schema/leaf-validators.ts +39 -9
  242. package/src/config/schema/main-account-hard-lock-fallback.ts +26 -0
  243. package/src/config/schema/native-reasoning-retention.ts +26 -0
  244. package/src/config.ts +2 -0
  245. package/src/generated/compatibility-version.json +962 -350
  246. package/src/generated/model-metadata.ts +1 -1
  247. package/src/grok/sync.ts +1 -1
  248. package/src/images/loop.ts +46 -8
  249. package/src/integrations/catalog-refresh.ts +1 -1
  250. package/src/integrations/config-io.ts +20 -2
  251. package/src/integrations/journal.ts +15 -1
  252. package/src/integrations/mutation-plan.ts +2 -0
  253. package/src/integrations/registry.ts +15 -0
  254. package/src/integrations/store.ts +3 -0
  255. package/src/integrations/writer.ts +10 -1
  256. package/src/lib/advisor-activation.ts +16 -0
  257. package/src/lib/background-work.ts +65 -0
  258. package/src/lib/bounded-file-read.ts +58 -0
  259. package/src/lib/bun-path-runtime.d.mts +10 -1
  260. package/src/lib/bun-path-runtime.mjs +62 -3
  261. package/src/lib/codex-restart-contract.ts +15 -0
  262. package/src/lib/desktop-proxy-route.ts +30 -0
  263. package/src/lib/desktop-upstream-tunnel.ts +72 -0
  264. package/src/lib/empty-tool-catalog.ts +62 -0
  265. package/src/lib/errors.ts +26 -3
  266. package/src/lib/gcp-adc.ts +96 -9
  267. package/src/lib/gcp-credential-input.ts +12 -0
  268. package/src/lib/lab-live-route-production.ts +5 -0
  269. package/src/lib/local-internal-call-capability.ts +55 -0
  270. package/src/lib/opaque-proxy-tunnel.ts +217 -0
  271. package/src/lib/package-tree-integrity.ts +2 -2
  272. package/src/lib/redact.ts +2 -2
  273. package/src/lib/socks5-fetch.ts +48 -71
  274. package/src/lib/socks5-handshake.ts +112 -0
  275. package/src/lib/translator-budget.ts +7 -1
  276. package/src/oauth/account-quota-rank.ts +69 -2
  277. package/src/oauth/anthropic-account-refusal.ts +20 -2
  278. package/src/oauth/anthropic-oauth-definitions.ts +3 -1
  279. package/src/oauth/anthropic-routing.ts +1 -21
  280. package/src/oauth/anthropic.ts +38 -7
  281. package/src/oauth/callback-server.ts +27 -1
  282. package/src/oauth/chatgpt-device.ts +1 -1
  283. package/src/oauth/chatgpt-shared.ts +83 -0
  284. package/src/oauth/chatgpt.ts +8 -72
  285. package/src/oauth/dsh.ts +203 -0
  286. package/src/oauth/generic-account-failover.ts +62 -12
  287. package/src/oauth/generic-session-affinity.ts +125 -0
  288. package/src/oauth/index.ts +188 -111
  289. package/src/oauth/kiro-device-login.ts +2 -1
  290. package/src/oauth/kiro.ts +7 -1
  291. package/src/oauth/local-token-detect.ts +21 -1
  292. package/src/oauth/login-cli-errors.ts +44 -0
  293. package/src/oauth/login-cli.ts +69 -7
  294. package/src/oauth/login-flow-state.ts +371 -23
  295. package/src/oauth/meta-muse-errors.ts +41 -0
  296. package/src/oauth/meta-muse-keychain.ts +52 -0
  297. package/src/oauth/meta-muse.ts +51 -84
  298. package/src/oauth/model-discovery-auth.ts +3 -0
  299. package/src/oauth/pool-settings-capability.ts +4 -2
  300. package/src/oauth/refresh-flight-retirement.ts +48 -0
  301. package/src/oauth/session-affinity-key.ts +23 -0
  302. package/src/oauth/store-anthropic-instance.ts +9 -2
  303. package/src/oauth/store.ts +12 -5
  304. package/src/protocols/codecs/chat-ir.ts +467 -0
  305. package/src/protocols/codecs/chat.ts +10 -1
  306. package/src/protocols/codecs/messages-ir.ts +500 -0
  307. package/src/protocols/encoders/adapter-events.ts +5 -4
  308. package/src/protocols/encoders/messages.ts +9 -6
  309. package/src/protocols/features.ts +10 -2
  310. package/src/protocols/path.ts +11 -1
  311. package/src/protocols/plan-snapshot.ts +39 -16
  312. package/src/protocols/plan.ts +9 -1
  313. package/src/protocols/provider-summary.ts +2 -1
  314. package/src/protocols/settings.ts +8 -0
  315. package/src/providers/antigravity-account-roster.ts +102 -0
  316. package/src/providers/antigravity-models.ts +85 -7
  317. package/src/providers/api-key-cooldowns.ts +49 -0
  318. package/src/providers/api-key-resolve.ts +11 -6
  319. package/src/providers/api-key-response-ownership.ts +57 -0
  320. package/src/providers/api-key-selection.ts +31 -6
  321. package/src/providers/api-keys.ts +214 -3
  322. package/src/providers/claude-cli-usage.ts +96 -0
  323. package/src/providers/dsh-account-destination.ts +31 -0
  324. package/src/providers/dsh-account-models.ts +41 -0
  325. package/src/providers/gcp-credential-cleanup.ts +59 -0
  326. package/src/providers/key-failover.ts +54 -50
  327. package/src/providers/key-store.ts +7 -2
  328. package/src/providers/model-rename-fields.ts +5 -0
  329. package/src/providers/model-rename-migration.ts +7 -5
  330. package/src/providers/openai-tiers-destination.ts +11 -2
  331. package/src/providers/openai-tiers.ts +5 -12
  332. package/src/providers/opencode-go-transport.ts +3 -1
  333. package/src/providers/quota/account-cache.ts +6 -5
  334. package/src/providers/quota/account-row.ts +34 -0
  335. package/src/providers/quota/antigravity.ts +28 -7
  336. package/src/providers/quota/dsh-account.ts +70 -0
  337. package/src/providers/quota/vendor-probes-oauth.ts +1 -93
  338. package/src/providers/quota-types.ts +2 -0
  339. package/src/providers/quota.ts +14 -20
  340. package/src/providers/registry/entries-core.ts +44 -14
  341. package/src/providers/registry/entries-extended.ts +19 -0
  342. package/src/providers/registry/types.ts +1 -1
  343. package/src/providers/request-pacing.ts +77 -19
  344. package/src/providers/routed-config.ts +339 -0
  345. package/src/providers/service-tier.ts +2 -1
  346. package/src/quota/window-mapping.ts +5 -2
  347. package/src/responses/anthropic-progress.ts +34 -0
  348. package/src/responses/assistant-replay.ts +35 -0
  349. package/src/responses/compaction-history-framing.ts +65 -0
  350. package/src/responses/compaction.ts +6 -1
  351. package/src/responses/incomplete-reason.ts +31 -0
  352. package/src/responses/parser.ts +78 -22
  353. package/src/responses/reasoning-envelope.ts +4 -0
  354. package/src/responses/reasoning-replay-cache.ts +24 -2
  355. package/src/responses/reasoning-retention.ts +173 -0
  356. package/src/responses/retained-compaction.ts +46 -0
  357. package/src/responses/schema.ts +23 -0
  358. package/src/responses/state.ts +32 -29
  359. package/src/router.ts +8 -321
  360. package/src/routing/health.ts +37 -22
  361. package/src/routing/history/build-file.ts +92 -0
  362. package/src/routing/history/indexer.ts +254 -221
  363. package/src/routing/history/ingest.ts +242 -0
  364. package/src/routing/history/rebuild-job.ts +111 -0
  365. package/src/routing/history/rebuild-protocol.ts +15 -0
  366. package/src/routing/history/rebuild-worker.ts +34 -0
  367. package/src/routing/profile.ts +1 -1
  368. package/src/server/admission-model-scope.ts +21 -1
  369. package/src/server/antigravity-direct-eligibility.ts +208 -0
  370. package/src/server/auth-cors.ts +15 -0
  371. package/src/server/chat-completions.ts +82 -10
  372. package/src/server/chat-native-reasoning.ts +123 -0
  373. package/src/server/chat-native-sse.ts +110 -7
  374. package/src/server/chat-native.ts +76 -17
  375. package/src/server/claude-messages.ts +164 -433
  376. package/src/server/effort-policy.ts +1 -1
  377. package/src/server/images.ts +15 -2
  378. package/src/server/index/desktop-compatibility-startup.ts +56 -0
  379. package/src/server/index/model-alias-id.ts +36 -0
  380. package/src/server/index/optional-listeners.ts +2 -0
  381. package/src/server/index/serve-options.ts +11 -3
  382. package/src/server/index/startup-warnings.ts +11 -0
  383. package/src/server/index/websocket-handler.ts +9 -0
  384. package/src/server/index.ts +14 -11
  385. package/src/server/inference/advisor-diagnostics.ts +31 -0
  386. package/src/server/inference/client-encoder-delivery.ts +6 -5
  387. package/src/server/login-shell-path.ts +146 -0
  388. package/src/server/management/advisor-routes.ts +202 -0
  389. package/src/server/management/agent-settings-routes.ts +22 -4
  390. package/src/server/management/claude-desktop-picker-routes.ts +1 -1
  391. package/src/server/management/combo-routes.ts +176 -50
  392. package/src/server/management/companion-routes.ts +4 -0
  393. package/src/server/management/config-routes.ts +77 -6
  394. package/src/server/management/context.ts +4 -0
  395. package/src/server/management/cursor-integration-routes.ts +1 -1
  396. package/src/server/management/desktop-compatibility-routes.ts +46 -0
  397. package/src/server/management/desktop-compatibility-runtime-routes.ts +46 -0
  398. package/src/server/management/desktop-compatibility-settings-routes.ts +27 -0
  399. package/src/server/management/model-routes.ts +13 -8
  400. package/src/server/management/model-rows.ts +14 -0
  401. package/src/server/management/native-integration-routes.ts +2 -7
  402. package/src/server/management/native-reasoning-retention-routes.ts +41 -0
  403. package/src/server/management/oauth-account-routes.ts +87 -14
  404. package/src/server/management/protocol-settings-patch.ts +1 -0
  405. package/src/server/management/provider-gcp-credential-routes.ts +29 -0
  406. package/src/server/management/provider-key-pacing-routes.ts +31 -0
  407. package/src/server/management/provider-overwrite-carry.ts +7 -0
  408. package/src/server/management/provider-routes.ts +25 -30
  409. package/src/server/management/route-registry.ts +21 -1
  410. package/src/server/management/sibling-guard.ts +1 -0
  411. package/src/server/management-api.ts +9 -1
  412. package/src/server/messages-antigravity-direct.ts +195 -0
  413. package/src/server/messages-native-tool-guard.ts +185 -0
  414. package/src/server/messages-native.ts +106 -22
  415. package/src/server/messages-shared.ts +431 -0
  416. package/src/server/relay.ts +1 -1
  417. package/src/server/request-log-terminal-status.ts +1 -1
  418. package/src/server/request-log.ts +13 -1
  419. package/src/server/responses/account-change-state.ts +8 -2
  420. package/src/server/responses/adapter-continuation.ts +48 -19
  421. package/src/server/responses/adapter-delivery.ts +43 -8
  422. package/src/server/responses/adapter-dispatch.ts +48 -4
  423. package/src/server/responses/advisor-plan-slot.ts +27 -0
  424. package/src/server/responses/advisor-retention.ts +117 -0
  425. package/src/server/responses/advisor-slot.ts +368 -0
  426. package/src/server/responses/code-mode-collab-surface.ts +116 -8
  427. package/src/server/responses/codex-ws-wire.ts +15 -8
  428. package/src/server/responses/compact.ts +303 -125
  429. package/src/server/responses/compaction-recovery.ts +18 -10
  430. package/src/server/responses/compaction-retention.ts +61 -0
  431. package/src/server/responses/context-overflow.ts +11 -6
  432. package/src/server/responses/core-auth.ts +6 -0
  433. package/src/server/responses/core-codex-account.ts +11 -1
  434. package/src/server/responses/core-combo-failure.ts +12 -1
  435. package/src/server/responses/core-combo.ts +141 -36
  436. package/src/server/responses/core-errors.ts +3 -3
  437. package/src/server/responses/core-lifetime.ts +7 -2
  438. package/src/server/responses/core-normalize.ts +11 -0
  439. package/src/server/responses/core-opaque-recovery.ts +35 -0
  440. package/src/server/responses/core-options.ts +51 -0
  441. package/src/server/responses/core-pipeline.ts +141 -0
  442. package/src/server/responses/core-replay.ts +25 -6
  443. package/src/server/responses/core.ts +3 -116
  444. package/src/server/responses/fetch-helpers.ts +73 -19
  445. package/src/server/responses/main-hard-lock-routing.ts +109 -0
  446. package/src/server/responses/native-response-control.ts +4 -2
  447. package/src/server/responses/passthrough-delivery.ts +19 -11
  448. package/src/server/responses/passthrough-dispatch.ts +29 -16
  449. package/src/server/responses/request-prepare.ts +144 -17
  450. package/src/server/responses/request-sidecar-auth.ts +2 -2
  451. package/src/server/responses/request-transport.ts +120 -27
  452. package/src/server/responses/response-effects.ts +1 -0
  453. package/src/server/responses/run-turn-execution.ts +166 -38
  454. package/src/server/responses/sidecar-execution.ts +98 -5
  455. package/src/server/responses/terminal-guard.ts +4 -17
  456. package/src/server/responses-undeclared-tool-guard.ts +30 -9
  457. package/src/server/startup-action-control.ts +16 -2
  458. package/src/server/startup-health-cache.ts +1 -0
  459. package/src/service/auth-preflight.ts +71 -0
  460. package/src/service/cli.ts +4 -2
  461. package/src/service/definition.ts +97 -0
  462. package/src/service/desktop-startup.ts +14 -0
  463. package/src/service/guards.ts +1 -61
  464. package/src/service/health-sentinel-linux.ts +112 -0
  465. package/src/service/health-sentinel-main.ts +99 -61
  466. package/src/service/health-sentinel-probe.ts +24 -7
  467. package/src/service/health.ts +5 -89
  468. package/src/service/launchd.ts +27 -7
  469. package/src/service/logging.ts +135 -0
  470. package/src/service/repair.ts +2 -1
  471. package/src/service/state.ts +7 -8
  472. package/src/service/systemd.ts +1 -1
  473. package/src/service/windows-guarded-manager.ts +2 -1
  474. package/src/service/windows-ops.ts +1 -1
  475. package/src/service/windows-scheduler.ts +2 -7
  476. package/src/service/windows-taskxml.ts +2 -2
  477. package/src/service/windows-wscript.ts +14 -0
  478. package/src/service.ts +4 -2
  479. package/src/sidecar/auth.ts +2 -0
  480. package/src/tray/windows-tray.ps1 +43 -24
  481. package/src/types/config.ts +124 -6
  482. package/src/types/opencode-go.ts +61 -0
  483. package/src/types/opencode-zen.ts +29 -0
  484. package/src/types/provider.ts +39 -6
  485. package/src/types/request.ts +38 -2
  486. package/src/types/tools.ts +2 -0
  487. package/src/types/wire.ts +29 -37
  488. package/src/types.ts +1 -0
  489. package/src/update/async-check.ts +1 -1
  490. package/src/update/badge.ts +2 -2
  491. package/src/update/index.ts +88 -15
  492. package/src/update/job-supervised-restart.ts +107 -0
  493. package/src/update/job.ts +32 -32
  494. package/src/update/notify.ts +1 -1
  495. package/src/update/npm-invocation.mjs +12 -2
  496. package/src/update/refresh-scheduler.ts +2 -2
  497. package/src/update/supervised-recovery.d.mts +27 -0
  498. package/src/update/supervised-recovery.mjs +157 -0
  499. package/src/usage/cache-diagnostic.ts +2 -0
  500. package/src/usage/cost.ts +48 -12
  501. package/src/usage/expected-prices.ts +15 -0
  502. package/src/usage/ledger-retention.ts +2 -1
  503. package/src/usage/ledger-scanner.ts +6 -5
  504. package/src/usage/log.ts +17 -8
  505. package/src/usage/summary.ts +22 -9
  506. package/src/usage/user-cost-overlays.ts +14 -19
  507. package/src/usage/user-cost-validation.ts +38 -0
  508. package/src/vision/eligibility.ts +13 -0
  509. package/src/web-search/gemini-executor.ts +2 -2
  510. package/src/web-search/loop.ts +55 -5
  511. package/src/web-search/progress-stream.ts +4 -1
  512. package/src/web-search/run-turn-loop.ts +9 -1
  513. package/assets/sponsors/packycode.png +0 -0
  514. package/gui/dist/assets/App-CSJvmjNs.css +0 -1
  515. package/gui/dist/assets/App-CZtqiGcw.js +0 -56
  516. package/gui/dist/assets/index-BB6uK6tU.css +0 -1
  517. package/gui/dist/assets/index-CTm4l-HQ.js +0 -94
  518. package/gui/dist/assets/quota-summary-BbEeRfPY.js +0 -1
@@ -31,6 +31,22 @@ export function bindAnthropicRefusalCredentialForSend(response: Response, owner:
31
31
  responseCredentials.set(response, Object.freeze({ ...owner, providerAccountUuid, checkProviderUuid: arguments.length >= 3 }));
32
32
  }
33
33
 
34
+ /** Preserve the original send proof across internal error normalization, without renewing it. */
35
+ export function carryAnthropicRefusalCredential(source: Response, target: Response): void {
36
+ const sent = responseCredentials.get(source);
37
+ if (sent) responseCredentials.set(target, sent);
38
+ }
39
+
40
+ /** Recheck after body consumption: a late refusal cannot acquire a replacement login's authority. */
41
+ export function anthropicRefusalCredentialIsCurrent(response: Response, instance: AnthropicInstanceId, accountId: string): boolean {
42
+ const sent = responseCredentials.get(response);
43
+ if (!sent || sent.provider !== instance || sent.accountId !== accountId
44
+ || !anthropicPhysicalSendOwnershipIsCurrent(sent)) return false;
45
+ const row = getAccountCredentialWithStatus(instance, accountId);
46
+ return !!row && !row.paused && !row.needsReauth
47
+ && (!sent.checkProviderUuid || row.credential.accountId === sent.providerAccountUuid);
48
+ }
49
+
34
50
  async function isRevokedOAuthToken(response: Response, signal?: AbortSignal): Promise<boolean> {
35
51
  if (response.status !== 401) return false;
36
52
  try {
@@ -59,9 +75,11 @@ async function isAccountRefusal(response: Response, signal?: AbortSignal): Promi
59
75
  // Whole-message patterns: request-policy/model/resource refusals and quoted diagnostics
60
76
  // must not acquire account authority merely by containing entitlement keywords.
61
77
  if ("code" in error && error.code != null
62
- && (typeof error.code !== "string" || !["subscription_required", "insufficient_quota", "permission_denied"].includes(error.code))) return false;
78
+ && (typeof error.code !== "string" || !["subscription_required", "insufficient_quota", "permission_denied", "oauth_org_not_allowed"].includes(error.code))) return false;
79
+ // The organization disabled subscription access: an account-scoped verdict, cooled like its siblings.
80
+ if (error.type === "oauth_org_not_allowed" || ("code" in error && error.code === "oauth_org_not_allowed")) return true;
63
81
  const message = error.message.trim();
64
- if (error.type === "permission_error" && /^(?:Your account does not have access to Claude Code|Your (?:Claude )?subscription (?:has expired|is (?:expired|inactive))|Your account does not have an active subscription)[.!]?$/i.test(message)) return true;
82
+ if (error.type === "permission_error" && /^(?:Your account does not have access to Claude Code|Your (?:Claude )?subscription (?:has expired|is (?:expired|inactive))|Your account does not have an active subscription|(?:Your|This) organization has disabled Claude subscription access for Claude Code(?:\. Use an Anthropic API key instead, or ask your admin to enable access)?)[.!]?$/i.test(message)) return true;
65
83
  return (error.type === "billing_error" || error.type === "permission_error")
66
84
  && /^Your credit balance is too low to access the Anthropic API\.(?: Please go to Plans & Billing to upgrade or purchase credits\.)?$/i.test(message);
67
85
  } catch {
@@ -1,7 +1,7 @@
1
1
  import { ConfigMutationLockError } from "../config";
2
2
  import { deriveOAuthDefaultModel, deriveOAuthProviderConfig } from "../providers/derive";
3
3
  import type { AnthropicInstanceId } from "../providers/anthropic-instance-id";
4
- import { loginAnthropic, refreshAnthropicToken } from "./anthropic";
4
+ import { ANTHROPIC_CODE_REDIRECT_URI, loginAnthropic, refreshAnthropicToken } from "./anthropic";
5
5
  import type { OAuthProviderDef } from "./index";
6
6
  import {
7
7
  clearOAuthRefreshIntent, clearOAuthRefreshIntentIfMatch, markOAuthRefreshIntentCleanupPending,
@@ -16,8 +16,10 @@ export function anthropicOAuthDefinition(instance: AnthropicInstanceId): OAuthPr
16
16
  return {
17
17
  login: (ctrl, opts) => loginAnthropic(ctrl, {
18
18
  instance, importLocal: instance === "anthropic2" || opts?.forceLogin ? "off" : "fallback",
19
+ codeMode: opts?.codeMode === true,
19
20
  }),
20
21
  refresh: refreshAnthropicToken,
22
+ codeRedirectUri: ANTHROPIC_CODE_REDIRECT_URI,
21
23
  providerConfig,
22
24
  defaultModel,
23
25
  // Subscription OAuth must never generate unattended traffic by default.
@@ -19,7 +19,6 @@ import { clearAllAnthropicFamilyQuota, anthropicModelQuotaFor, anthropicModelPer
19
19
  */
20
20
  import { clearAllAnthropicRatePauses, anthropicRatePolicyFor, classifyAnthropic429, anthropicRetryAfterMs } from "./anthropic-rate-limit-policy";
21
21
  import { REFRESH_SKEW_MS } from "./refresh-policy";
22
- import { createHash } from "node:crypto";
23
22
  import { captureOAuthAccountSelection, commitOAuthAccountSelection, credentialGeneration, getAccountSet, getAccountCredential, getAccountCredentialWithStatus } from "./store";
24
23
  import type { OAuthAccessSnapshot } from "./index";
25
24
  import { getCachedProviderAccountQuota } from "../providers/quota";
@@ -273,26 +272,7 @@ export function formatAnthropicProviderForLog(
273
272
  return `${providerName}-${formatAnthropicAccountOrdinal(accountId)}`;
274
273
  }
275
274
 
276
- export function anthropicSessionKeyFromParts(input: {
277
- sessionIdHeader?: string | null;
278
- threadIdHeader?: string | null;
279
- promptCacheKey?: string | null;
280
- clientThreadId?: string | null;
281
- /** When true, prompt_cache_key is a shared Desktop cohort — ignore it for affinity. */
282
- promptCacheKeyIsSharedCohort?: boolean;
283
- }): string | null {
284
- const preferred = input.clientThreadId?.trim()
285
- || input.sessionIdHeader?.trim()
286
- || input.threadIdHeader?.trim()
287
- || "";
288
- if (preferred) {
289
- return preferred.length <= 128 ? preferred : createHash("sha256").update(preferred).digest("hex");
290
- }
291
- if (input.promptCacheKeyIsSharedCohort) return null;
292
- const cacheKey = input.promptCacheKey?.trim() ?? "";
293
- if (!cacheKey) return null;
294
- return cacheKey.length <= 128 ? cacheKey : createHash("sha256").update(cacheKey).digest("hex");
295
- }
275
+ export { oauthSessionKeyFromParts as anthropicSessionKeyFromParts } from "./session-affinity-key";
296
276
 
297
277
 
298
278
  export type AnthropicCooldownRecoveryClaim = Readonly<{
@@ -15,6 +15,16 @@ const CALLBACK_PATH = "/callback";
15
15
  /** Profile enrichment budget on token refresh; login paths keep the resolver's own deadline. */
16
16
  const REFRESH_PROFILE_TIMEOUT_MS = 2_000;
17
17
  const SCOPES = "org:create_api_key user:profile user:inference";
18
+ /**
19
+ * Provider-hosted code-display redirect: Anthropic renders `code#state` for the user to copy
20
+ * instead of redirecting to a local port. This is what makes login possible from a container or
21
+ * a hosted gateway, where no browser can reach this process. Same OAuth client as the callback
22
+ * flow — only the redirect differs, so it must be the redirect_uri used for BOTH the authorize
23
+ * request and the token exchange.
24
+ */
25
+ export const ANTHROPIC_CODE_REDIRECT_URI = "https://platform.claude.com/oauth/code/callback";
26
+ /** A displayed code is copied by hand across devices; the 300s callback wait is too short. */
27
+ const CODE_MODE_TIMEOUT_MS = 10 * 60_000;
18
28
 
19
29
  // ── OAuth-request requirements applied by the anthropic adapter when authMode==="oauth" ──
20
30
  export const ANTHROPIC_OAUTH_BETA = "claude-code-20250219,oauth-2025-04-20";
@@ -142,10 +152,27 @@ async function withAccountProfile(credential: OAuthCredentials, signal?: AbortSi
142
152
  }
143
153
 
144
154
  export class AnthropicOAuthFlow extends OAuthCallbackFlow {
155
+ /**
156
+ * PKCE verifier for THIS flow. One `AnthropicOAuthFlow` is constructed per login and
157
+ * `generateAuthUrl` runs once on it, so the verifier is already flow-scoped: a second
158
+ * concurrent login builds a second instance and cannot reach this field.
159
+ */
145
160
  #verifier = "";
161
+ #codeMode: boolean;
146
162
 
147
- constructor(ctrl: OAuthController) {
148
- super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
163
+ constructor(ctrl: OAuthController, opts?: { codeMode?: boolean }) {
164
+ if (opts?.codeMode) {
165
+ super(ctrl, {
166
+ preferredPort: CALLBACK_PORT,
167
+ callbackPath: CALLBACK_PATH,
168
+ redirectUri: ANTHROPIC_CODE_REDIRECT_URI,
169
+ skipCallbackServer: true,
170
+ timeoutMs: CODE_MODE_TIMEOUT_MS,
171
+ });
172
+ } else {
173
+ super(ctrl, CALLBACK_PORT, CALLBACK_PATH);
174
+ }
175
+ this.#codeMode = opts?.codeMode === true;
149
176
  }
150
177
 
151
178
  async generateAuthUrl(state: string, redirectUri: string): Promise<{ url: string; instructions?: string }> {
@@ -163,8 +190,9 @@ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
163
190
  });
164
191
  return {
165
192
  url: `${AUTHORIZE_URL}?${authParams.toString()}`,
166
- instructions:
167
- "Complete Claude login in your browser. If the browser cannot reach this machine, paste the final redirect URL or authorization code when prompted.",
193
+ instructions: this.#codeMode
194
+ ? "Open this URL in any browser, approve access, then paste the code Anthropic displays (it looks like code#state)."
195
+ : "Complete Claude login in your browser. If the browser cannot reach this machine, paste the final redirect URL or authorization code when prompted.",
168
196
  };
169
197
  }
170
198
 
@@ -191,9 +219,12 @@ export class AnthropicOAuthFlow extends OAuthCallbackFlow {
191
219
 
192
220
  export async function loginAnthropic(
193
221
  ctrl: OAuthController,
194
- opts?: { importLocal?: LocalTokenImportMode; instance?: AnthropicInstanceId },
222
+ opts?: { importLocal?: LocalTokenImportMode; instance?: AnthropicInstanceId; codeMode?: boolean },
195
223
  ): Promise<OAuthCredentials> {
196
- const importLocal = opts?.importLocal ?? "off";
224
+ // A code-mode caller asked for a URL to show and a box to paste into. Importing a local
225
+ // Claude Code token instead would complete WITHOUT ever firing onAuth, so the caller would
226
+ // get no URL at all — a different outcome than the one its contract promises.
227
+ const importLocal = opts?.codeMode ? "off" : opts?.importLocal ?? "off";
197
228
  if (opts?.instance === "anthropic2" && importLocal !== "off") throw new AnthropicLocalCliImportError();
198
229
  if (importLocal !== "off") {
199
230
  const { detectClaudeCodeToken } = await import("./local-token-detect");
@@ -221,7 +252,7 @@ export async function loginAnthropic(
221
252
  );
222
253
  }
223
254
  }
224
- return new AnthropicOAuthFlow(ctrl).login();
255
+ return new AnthropicOAuthFlow(ctrl, { codeMode: opts?.codeMode === true }).login();
225
256
  }
226
257
 
227
258
  export async function refreshAnthropicToken(refreshToken: string): Promise<OAuthCredentials> {
@@ -66,6 +66,15 @@ export interface OAuthCallbackFlowOptions {
66
66
  callbackBindHostname?: string;
67
67
  /** Exact redirect URI advertised to the provider; disables port fallback. */
68
68
  redirectUri?: string;
69
+ /**
70
+ * Code-display mode: the provider renders the authorization code for the user to paste, so
71
+ * this process never receives a redirect and must NOT open a listener. Requires `redirectUri`
72
+ * (the provider-hosted code page) and an `onManualCodeInput` channel — without one the flow
73
+ * would wait for a callback that can never arrive.
74
+ */
75
+ skipCallbackServer?: boolean;
76
+ /** Overrides the 300s wait for the code; code-display pastes are hand-typed and slower. */
77
+ timeoutMs?: number;
69
78
  }
70
79
 
71
80
  type BunServer = ReturnType<typeof Bun.serve>;
@@ -77,6 +86,8 @@ export abstract class OAuthCallbackFlow {
77
86
  callbackHostname: string;
78
87
  callbackBindHostname: string;
79
88
  redirectUri?: string;
89
+ skipCallbackServer: boolean;
90
+ timeoutMs: number;
80
91
  #callbackResolve?: (result: CallbackResult) => void;
81
92
  #callbackReject?: (error: string) => void;
82
93
 
@@ -86,6 +97,8 @@ export abstract class OAuthCallbackFlow {
86
97
  callbackPath: string = CALLBACK_PATH,
87
98
  ) {
88
99
  this.ctrl = ctrl;
100
+ this.skipCallbackServer = false;
101
+ this.timeoutMs = DEFAULT_TIMEOUT;
89
102
  if (typeof preferredPortOrOptions === "number") {
90
103
  this.preferredPort = preferredPortOrOptions;
91
104
  this.callbackPath = callbackPath;
@@ -98,6 +111,11 @@ export abstract class OAuthCallbackFlow {
98
111
  this.callbackHostname = preferredPortOrOptions.callbackHostname ?? DEFAULT_HOSTNAME;
99
112
  this.callbackBindHostname = preferredPortOrOptions.callbackBindHostname ?? DEFAULT_BIND_HOSTNAME;
100
113
  this.redirectUri = preferredPortOrOptions.redirectUri;
114
+ this.skipCallbackServer = preferredPortOrOptions.skipCallbackServer === true;
115
+ this.timeoutMs = preferredPortOrOptions.timeoutMs ?? DEFAULT_TIMEOUT;
116
+ if (this.skipCallbackServer && !this.redirectUri) {
117
+ throw new Error("skipCallbackServer requires an explicit redirectUri");
118
+ }
101
119
  }
102
120
 
103
121
  /** Build provider-specific authorization URL. */
@@ -132,6 +150,14 @@ export abstract class OAuthCallbackFlow {
132
150
  }
133
151
 
134
152
  async #startCallbackServer(expectedState: string): Promise<{ servers: BunServer[]; redirectUri: string }> {
153
+ if (this.skipCallbackServer) {
154
+ // The paste channel is the ONLY way this flow can complete. Fail loudly here rather than
155
+ // hanging until the timeout on a controller that can never deliver a code.
156
+ if (!this.ctrl.onManualCodeInput) {
157
+ throw new Error("Code-display login requires a manual code input channel");
158
+ }
159
+ return { servers: [], redirectUri: this.redirectUri! };
160
+ }
135
161
  try {
136
162
  const servers = this.#createServers(this.preferredPort, expectedState);
137
163
  if (this.redirectUri) {
@@ -226,7 +252,7 @@ export abstract class OAuthCallbackFlow {
226
252
  }
227
253
 
228
254
  #waitForCallback(expectedState: string): Promise<CallbackResult> {
229
- const timeoutSignal = AbortSignal.timeout(DEFAULT_TIMEOUT);
255
+ const timeoutSignal = AbortSignal.timeout(this.timeoutMs);
230
256
  const signal = this.ctrl.signal ? AbortSignal.any([this.ctrl.signal, timeoutSignal]) : timeoutSignal;
231
257
 
232
258
  const callbackPromise = new Promise<CallbackResult>((resolve, reject) => {
@@ -1,5 +1,5 @@
1
1
  import type { OAuthController, OAuthCredentials } from "./types";
2
- import { CHATGPT_CLIENT_ID, CHATGPT_TOKEN_URL, credsFromToken } from "./chatgpt";
2
+ import { CHATGPT_CLIENT_ID, CHATGPT_TOKEN_URL, credsFromToken } from "./chatgpt-shared";
3
3
 
4
4
  /**
5
5
  * OpenAI deviceauth (device-code) grant for the ChatGPT/Codex provider.
@@ -0,0 +1,83 @@
1
+ import type { OAuthCredentials } from "./types";
2
+
3
+ /**
4
+ * What the browser callback grant and the deviceauth grant share: the public PKCE client and the
5
+ * way a token response becomes credentials.
6
+ *
7
+ * This is a leaf on purpose. `./chatgpt-device` needs these, and `./chatgpt` needs the device
8
+ * grant for `flow: "device"`; importing them straight from `./chatgpt` made the pair a cycle.
9
+ */
10
+
11
+ const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
12
+ const TOKEN_URL = "https://auth.openai.com/oauth/token";
13
+
14
+ /** Shared with the deviceauth grant in `./chatgpt-device`: same public PKCE client. */
15
+ export const CHATGPT_CLIENT_ID = CLIENT_ID;
16
+ export const CHATGPT_TOKEN_URL = TOKEN_URL;
17
+
18
+ export function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
19
+ const parts = token.split(".");
20
+ if (parts.length !== 3 || !parts[1]) return undefined;
21
+ try {
22
+ return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")) as Record<string, unknown>;
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ }
27
+
28
+ export function extractAccountId(idToken?: string, accessToken?: string): string | undefined {
29
+ for (const token of [idToken, accessToken]) {
30
+ if (!token) continue;
31
+ const payload = decodeJwtPayload(token);
32
+ if (!payload) continue;
33
+ if (typeof payload.chatgpt_account_id === "string") return payload.chatgpt_account_id;
34
+ const ns = payload["https://api.openai.com/auth"];
35
+ if (ns && typeof ns === "object" && typeof (ns as Record<string, unknown>).chatgpt_account_id === "string") {
36
+ return (ns as Record<string, unknown>).chatgpt_account_id as string;
37
+ }
38
+ const orgs = payload.organizations;
39
+ if (Array.isArray(orgs) && orgs[0] && typeof orgs[0].id === "string") return orgs[0].id as string;
40
+ }
41
+ return undefined;
42
+ }
43
+
44
+ export function extractEmail(idToken?: string, accessToken?: string): string | undefined {
45
+ for (const token of [idToken, accessToken]) {
46
+ if (!token) continue;
47
+ const payload = decodeJwtPayload(token);
48
+ if (!payload) continue;
49
+ if (typeof payload.email === "string") return payload.email.toLowerCase();
50
+ }
51
+ return undefined;
52
+ }
53
+
54
+ export function credsFromToken(data: Record<string, unknown>): OAuthCredentials {
55
+ const idToken = typeof data.id_token === "string" ? data.id_token : undefined;
56
+ // This parses a response from an external boundary, so the access token is
57
+ // validated rather than cast. A 200 carrying no access_token would otherwise
58
+ // resolve a login as successful with an undefined credential, which then gets
59
+ // silently declined at persistence — a success message and no account.
60
+ const accessToken = typeof data.access_token === "string" && data.access_token.length > 0
61
+ ? data.access_token
62
+ : undefined;
63
+ if (!accessToken) throw new Error("ChatGPT token response missing access token");
64
+ const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : "";
65
+ // ?? only guards null/undefined; NaN or a string expires_in would otherwise
66
+ // produce a NaN expiry that never compares as expired, and a negative duration
67
+ // would stamp an already-past expiry — both block refresh semantics.
68
+ const expiresIn =
69
+ typeof data.expires_in === "number" && Number.isFinite(data.expires_in) && data.expires_in >= 0
70
+ ? data.expires_in
71
+ : 3600;
72
+ // The computed timestamp itself must stay finite: Number.MAX_VALUE passes
73
+ // Number.isFinite but overflows to Infinity once multiplied by 1000.
74
+ const computedExpires = Date.now() + expiresIn * 1000;
75
+ const expires = Number.isFinite(computedExpires) ? computedExpires : Date.now() + 3600 * 1000;
76
+ return {
77
+ access: accessToken,
78
+ refresh: refreshToken,
79
+ expires,
80
+ accountId: extractAccountId(idToken, accessToken),
81
+ email: extractEmail(idToken, accessToken),
82
+ };
83
+ }
@@ -1,4 +1,5 @@
1
1
  import { OAuthCallbackFlow } from "./callback-server";
2
+ import { CHATGPT_CLIENT_ID, CHATGPT_TOKEN_URL, credsFromToken, decodeJwtPayload, extractAccountId, extractEmail } from "./chatgpt-shared";
2
3
  import type { OAuthController, OAuthCredentials } from "./types";
3
4
  import { generatePKCE } from "./pkce";
4
5
  import { classifyChatgptRefreshFailure } from "../codex/chatgpt-refresh-failure";
@@ -102,44 +103,20 @@ function chatGptTokenError(status: number, body: string, label: string): ChatGpt
102
103
  );
103
104
  }
104
105
 
105
- const CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
106
+ /**
107
+ * The client and token decoding live in a leaf shared with the device grant.
108
+ * Re-exported so existing import sites keep their public helper surface.
109
+ */
110
+ export { CHATGPT_CLIENT_ID, CHATGPT_TOKEN_URL, credsFromToken, decodeJwtPayload, extractAccountId, extractEmail };
111
+ const CLIENT_ID = CHATGPT_CLIENT_ID;
106
112
  const AUTH_URL = "https://auth.openai.com/oauth/authorize";
107
- const TOKEN_URL = "https://auth.openai.com/oauth/token";
113
+ const TOKEN_URL = CHATGPT_TOKEN_URL;
108
114
 
109
- /** Shared with the deviceauth grant in `./chatgpt-device`: same public PKCE client. */
110
- export const CHATGPT_CLIENT_ID = CLIENT_ID;
111
- export const CHATGPT_TOKEN_URL = TOKEN_URL;
112
115
  const SCOPE = "openid profile email offline_access api.connectors.read api.connectors.invoke";
113
116
  const CALLBACK_PORT = 1455;
114
117
  const CALLBACK_PATH = "/auth/callback";
115
118
  const ORIGINATOR = "opencodex";
116
119
 
117
- export function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
118
- const parts = token.split(".");
119
- if (parts.length !== 3 || !parts[1]) return undefined;
120
- try {
121
- return JSON.parse(Buffer.from(parts[1], "base64url").toString("utf8")) as Record<string, unknown>;
122
- } catch {
123
- return undefined;
124
- }
125
- }
126
-
127
- export function extractAccountId(idToken?: string, accessToken?: string): string | undefined {
128
- for (const token of [idToken, accessToken]) {
129
- if (!token) continue;
130
- const payload = decodeJwtPayload(token);
131
- if (!payload) continue;
132
- if (typeof payload.chatgpt_account_id === "string") return payload.chatgpt_account_id;
133
- const ns = payload["https://api.openai.com/auth"];
134
- if (ns && typeof ns === "object" && typeof (ns as Record<string, unknown>).chatgpt_account_id === "string") {
135
- return (ns as Record<string, unknown>).chatgpt_account_id as string;
136
- }
137
- const orgs = payload.organizations;
138
- if (Array.isArray(orgs) && orgs[0] && typeof orgs[0].id === "string") return orgs[0].id as string;
139
- }
140
- return undefined;
141
- }
142
-
143
120
  /**
144
121
  * Three-way answer to "is this token marked as belonging to the ChatGPT account domain".
145
122
  * Only ChatGPT-specific claims count as markers: a top-level chatgpt_account_id or the
@@ -196,16 +173,6 @@ export function inspectChatGptDomainClaim(token: string): ChatGptDomainClaim {
196
173
  return accountId ? { kind: "valid", accountId } : { kind: "invalid" };
197
174
  }
198
175
 
199
- export function extractEmail(idToken?: string, accessToken?: string): string | undefined {
200
- for (const token of [idToken, accessToken]) {
201
- if (!token) continue;
202
- const payload = decodeJwtPayload(token);
203
- if (!payload) continue;
204
- if (typeof payload.email === "string") return payload.email.toLowerCase();
205
- }
206
- return undefined;
207
- }
208
-
209
176
  /**
210
177
  * Identity-agreement view of one token for security-sensitive bindings. `accountId` follows the
211
178
  * existing extractAccountId precedence (top-level, then namespaced, then organizations[0]).
@@ -233,37 +200,6 @@ export function extractAccountIdClaims(token?: string): { accountId: string | un
233
200
  };
234
201
  }
235
202
 
236
- export function credsFromToken(data: Record<string, unknown>): OAuthCredentials {
237
- const idToken = typeof data.id_token === "string" ? data.id_token : undefined;
238
- // This parses a response from an external boundary, so the access token is
239
- // validated rather than cast. A 200 carrying no access_token would otherwise
240
- // resolve a login as successful with an undefined credential, which then gets
241
- // silently declined at persistence — a success message and no account.
242
- const accessToken = typeof data.access_token === "string" && data.access_token.length > 0
243
- ? data.access_token
244
- : undefined;
245
- if (!accessToken) throw new Error("ChatGPT token response missing access token");
246
- const refreshToken = typeof data.refresh_token === "string" ? data.refresh_token : "";
247
- // ?? only guards null/undefined; NaN or a string expires_in would otherwise
248
- // produce a NaN expiry that never compares as expired, and a negative duration
249
- // would stamp an already-past expiry — both block refresh semantics.
250
- const expiresIn =
251
- typeof data.expires_in === "number" && Number.isFinite(data.expires_in) && data.expires_in >= 0
252
- ? data.expires_in
253
- : 3600;
254
- // The computed timestamp itself must stay finite: Number.MAX_VALUE passes
255
- // Number.isFinite but overflows to Infinity once multiplied by 1000.
256
- const computedExpires = Date.now() + expiresIn * 1000;
257
- const expires = Number.isFinite(computedExpires) ? computedExpires : Date.now() + 3600 * 1000;
258
- return {
259
- access: accessToken,
260
- refresh: refreshToken,
261
- expires,
262
- accountId: extractAccountId(idToken, accessToken),
263
- email: extractEmail(idToken, accessToken),
264
- };
265
- }
266
-
267
203
  export class ChatGPTOAuthFlow extends OAuthCallbackFlow {
268
204
  #verifier = "";
269
205
  forceLogin = false;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * DeepSeek Harness Account OAuth implementation (Import-first).
3
+ *
4
+ * Imports existing authenticated account from DeepSeek Harness Desktop
5
+ * (`$DSH_HOME/.credentials.yaml`, default `~/.dsh/.credentials.yaml`).
6
+ *
7
+ * Invariant: The DSH credential store is strictly READ-ONLY to OpenCodeX.
8
+ * OpenCodeX NEVER mutates, overwrites, deletes, or changes permissions of `~/.dsh/.credentials.yaml`.
9
+ */
10
+
11
+ import type { OAuthController, OAuthCredentials } from "./types";
12
+ import { homedir } from "node:os";
13
+ import { join } from "node:path";
14
+ import { existsSync, readFileSync } from "node:fs";
15
+
16
+ export const DSH_PLATFORM_ORIGIN = "https://platform.deepseek.com";
17
+ export const DSH_INFERENCE_ORIGIN = "https://api.deepseek.com/anthropic";
18
+
19
+ export interface DshAccountDetectionResult {
20
+ detected: boolean;
21
+ token?: string;
22
+ issuer?: string;
23
+ error?: string;
24
+ }
25
+
26
+ export interface DshValidatedAccountIdentity {
27
+ valid: boolean;
28
+ accountId?: string;
29
+ email?: string;
30
+ name?: string;
31
+ error?: string;
32
+ }
33
+
34
+ export function resolveDshCredentialsPath(): string {
35
+ const dshHome = process.env.DSH_HOME?.trim() || join(homedir(), ".dsh");
36
+ return join(dshHome, ".credentials.yaml");
37
+ }
38
+
39
+ /**
40
+ * Pure read-only probe to detect whether DeepSeek Harness Desktop is installed
41
+ * and signed in on this machine.
42
+ * NEVER writes, creates, mutates, or deletes any file.
43
+ */
44
+ export function detectDshAccount(): DshAccountDetectionResult {
45
+ const filePath = resolveDshCredentialsPath();
46
+ if (!existsSync(filePath)) {
47
+ return { detected: false };
48
+ }
49
+ try {
50
+ const content = readFileSync(filePath, "utf8");
51
+ const data = Bun.YAML.parse(content) as Record<string, unknown> | null;
52
+ if (!data || typeof data !== "object") {
53
+ return { detected: false };
54
+ }
55
+ const records = data.records as Record<string, unknown> | undefined;
56
+ const accountRecord = records?.["deepseek-account-platform/default"] as {
57
+ kind?: string;
58
+ payload?: { version?: number; token?: string; issuer?: string };
59
+ } | undefined;
60
+
61
+ if (accountRecord?.kind === "grant" && typeof accountRecord.payload?.token === "string" && accountRecord.payload.token.length > 0) {
62
+ return {
63
+ detected: true,
64
+ token: accountRecord.payload.token,
65
+ issuer: accountRecord.payload.issuer ?? DSH_PLATFORM_ORIGIN,
66
+ };
67
+ }
68
+ return { detected: false };
69
+ } catch {
70
+ return { detected: false, error: "CREDENTIAL_FILE_READ_FAILED" };
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Validate token against DeepSeek's authoritative profile endpoint:
76
+ * GET https://platform.deepseek.com/auth-api/v0/users/current
77
+ */
78
+ export async function validateDshAccountToken(
79
+ token: string,
80
+ signal?: AbortSignal,
81
+ ): Promise<DshValidatedAccountIdentity> {
82
+ try {
83
+ const timeoutSignal = AbortSignal.timeout(10_000);
84
+ const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
85
+ const response = await fetch(`${DSH_PLATFORM_ORIGIN}/auth-api/v0/users/current`, {
86
+ method: "GET",
87
+ headers: {
88
+ "x-dsh-auth-token": token,
89
+ "accept": "application/json",
90
+ },
91
+ redirect: "manual",
92
+ signal: combinedSignal,
93
+ });
94
+
95
+ if (response.status === 401) {
96
+ return { valid: false, error: "ACCOUNT_TOKEN_INVALID" };
97
+ }
98
+ if (!response.ok) {
99
+ return { valid: false, error: `Platform returned HTTP ${response.status}` };
100
+ }
101
+
102
+ const body = (await response.json()) as {
103
+ code?: number;
104
+ data?: {
105
+ biz_code?: number;
106
+ biz_data?: {
107
+ id?: string | null;
108
+ email?: string;
109
+ id_profile?: { name?: string | null; picture?: string | null };
110
+ };
111
+ };
112
+ };
113
+
114
+ if (body.code !== 0 || body.data?.biz_code !== 0 || !body.data.biz_data) {
115
+ return { valid: false, error: `Platform error code: ${body.code ?? "unknown"}` };
116
+ }
117
+
118
+ const biz = body.data.biz_data;
119
+ // Account-store slot matching and reauthentication require the same stable upstream ID.
120
+ // Email/phone/display names and a shared fallback must never identify an imported account.
121
+ const accountId = typeof biz.id === "string" ? biz.id.trim() : "";
122
+ if (!accountId) return { valid: false, error: "ACCOUNT_IDENTITY_UNVERIFIED" };
123
+ const email = typeof biz.email === "string" ? biz.email.trim() || undefined : undefined;
124
+ const name = typeof biz.id_profile?.name === "string" ? biz.id_profile.name.trim() || undefined : undefined;
125
+
126
+ return {
127
+ valid: true,
128
+ accountId,
129
+ email,
130
+ name,
131
+ };
132
+ } catch {
133
+ if (signal?.aborted) throw signal.reason ?? new DOMException("DSH login aborted", "AbortError");
134
+ // Parser and transport errors may contain profile data or credential-file contents.
135
+ return { valid: false, error: "PROFILE_VALIDATION_FAILED" };
136
+ }
137
+ }
138
+
139
+ export interface DshLoginOptions {
140
+ forceLogin?: boolean;
141
+ }
142
+
143
+ /**
144
+ * Explicit user login action for DSH Account.
145
+ * Reuses the existing DSH local credential after live authoritative validation.
146
+ */
147
+ export async function loginDshAccount(
148
+ ctrl: OAuthController,
149
+ _opts: DshLoginOptions = {},
150
+ ): Promise<OAuthCredentials> {
151
+ if (ctrl.signal?.aborted) {
152
+ throw ctrl.signal.reason ?? new DOMException("DSH login aborted", "AbortError");
153
+ }
154
+
155
+ const detection = detectDshAccount();
156
+ if (!detection.detected || !detection.token) {
157
+ throw new Error(
158
+ "DeepSeek Harness account not found in ~/.dsh/.credentials.yaml. " +
159
+ "Please sign in to DeepSeek Harness Desktop first, then import your account.",
160
+ );
161
+ }
162
+
163
+ const validation = await validateDshAccountToken(detection.token, ctrl.signal);
164
+ if (!validation.valid || !validation.accountId) {
165
+ throw new Error(
166
+ `DeepSeek Harness account token in ~/.dsh/.credentials.yaml is invalid or expired (${validation.error ?? "unknown"}). ` +
167
+ "Please re-login in DeepSeek Harness Desktop, then try again.",
168
+ );
169
+ }
170
+
171
+ return {
172
+ access: detection.token,
173
+ refresh: detection.token,
174
+ expires: Number.MAX_SAFE_INTEGER,
175
+ accountId: validation.accountId,
176
+ email: validation.email,
177
+ source: "credential-file",
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Token refresh for DSH Account.
183
+ * DSH accounts use long-lived session grant tokens without an automatic rotation endpoint.
184
+ * In accordance with the explicit-import contract, background or automatic refresh MUST NOT
185
+ * automatically re-read or adopt new credentials from ~/.dsh/.credentials.yaml.
186
+ * If the grant is rejected or expires, this throws a terminal error so the account transitions
187
+ * to needsReauth, requiring explicit user re-import.
188
+ */
189
+ export async function refreshDshAccountToken(
190
+ _refreshToken: string,
191
+ _signal?: AbortSignal,
192
+ _credential?: OAuthCredentials,
193
+ _accountId?: string,
194
+ ): Promise<OAuthCredentials> {
195
+ throw new Error("DeepSeek Harness account session expired or revoked. Please sign in to DeepSeek Harness Desktop and re-import.");
196
+ }
197
+
198
+ /** Provider-owned auth hooks keep the shared OAuth registry within its line budget. */
199
+ export const DSH_ACCOUNT_AUTH = {
200
+ login: loginDshAccount,
201
+ refresh: refreshDshAccountToken,
202
+ defaultRefreshPolicy: "disabled" as const,
203
+ };