@bitkyc08/opencodex 2.81.0 → 2.83.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 (272) hide show
  1. package/bin/ocx.mjs +166 -73
  2. package/gui/dist/assets/App-Ba8uG696.js +54 -0
  3. package/gui/dist/assets/{Tray-EwAOsP0S.js → Tray-IImbNPRy.js} +1 -1
  4. package/gui/dist/assets/{index-Brk6F3fn.css → index-BAbcj51J.css} +1 -1
  5. package/gui/dist/assets/{index-RsoaAxXY.js → index-BHRZdJuv.js} +13 -13
  6. package/gui/dist/assets/{quota-summary-D2lN8tDm.js → quota-summary-DiJO6sYY.js} +1 -1
  7. package/gui/dist/index.html +2 -2
  8. package/package.json +1 -1
  9. package/src/adapters/anthropic.ts +1 -1
  10. package/src/adapters/cursor/catalog.ts +2 -2
  11. package/src/adapters/cursor/effort-map.ts +1 -1
  12. package/src/adapters/devin/live-models.ts +2 -2
  13. package/src/adapters/google-truncation.ts +22 -0
  14. package/src/adapters/google.ts +7 -3
  15. package/src/adapters/ollama-native.ts +18 -14
  16. package/src/adapters/openai-responses/compaction-search-history.ts +109 -0
  17. package/src/adapters/openai-responses/passthrough.ts +11 -4
  18. package/src/adapters/openai-responses/plaintext-reasoning-notice.ts +85 -0
  19. package/src/adapters/openai-responses/reasoning.ts +11 -1
  20. package/src/adapters/openai-responses/tool-output-recovery.ts +20 -0
  21. package/src/bridge/errors.ts +7 -1
  22. package/src/claude/agents-inject.ts +14 -3
  23. package/src/claude/context-windows.ts +22 -20
  24. package/src/claude/desktop-3p.ts +88 -12
  25. package/src/claude/desktop-picker.ts +1 -1
  26. package/src/claude/desktop-profile.ts +2 -1
  27. package/src/claude/gateway-cache.ts +18 -1
  28. package/src/claude/inbound.ts +77 -3
  29. package/src/claude/intercept/cli-catalog.ts +3 -0
  30. package/src/claude/intercept/picker-ca-persistence.ts +7 -6
  31. package/src/claude/intercept/picker-models.ts +14 -6
  32. package/src/claude/intercept/settings.ts +3 -0
  33. package/src/claude/long-context.ts +66 -0
  34. package/src/claude/model-info.ts +19 -16
  35. package/src/claude/outbound.ts +94 -9
  36. package/src/claude/subagent-model.ts +40 -11
  37. package/src/cli/account-auth.ts +20 -12
  38. package/src/cli/agent.ts +9 -5
  39. package/src/cli/aside-profile-recovery.ts +33 -0
  40. package/src/cli/capabilities-base.ts +5 -5
  41. package/src/cli/capabilities-integrations.ts +2 -2
  42. package/src/cli/cli-path-diagnostics.ts +132 -0
  43. package/src/cli/desktop-runtime-guidance.ts +65 -0
  44. package/src/cli/dispatch.ts +5 -0
  45. package/src/cli/doctor.ts +16 -3
  46. package/src/cli/index.ts +14 -9
  47. package/src/cli/integration-aside-sync.ts +2 -1
  48. package/src/cli/integrations.ts +16 -3
  49. package/src/cli/resolve.ts +26 -3
  50. package/src/cli/root.ts +4 -2
  51. package/src/cli/status.ts +64 -12
  52. package/src/cli/update-restart-child.ts +4 -2
  53. package/src/cli/update-restart-home.ts +28 -10
  54. package/src/cli/update-restart-service-record.ts +116 -0
  55. package/src/cli/update-restart-supervision.ts +96 -0
  56. package/src/cli/update-restart.ts +59 -13
  57. package/src/cli/v2.ts +27 -20
  58. package/src/cli/version-skew-notice.ts +77 -0
  59. package/src/clients/config-export/model-metadata.ts +16 -0
  60. package/src/clients/config-export/zcode-store.ts +46 -11
  61. package/src/clients/config-export/zcode.ts +3 -5
  62. package/src/clients/config-export.ts +1 -1
  63. package/src/clients/lazycodex.ts +1 -1
  64. package/src/clients/omo-role-models.ts +32 -7
  65. package/src/codex/auth-api/account-selection.ts +107 -0
  66. package/src/codex/auth-api/pool-mode-gate.ts +4 -5
  67. package/src/codex/auth-api/routes.ts +10 -49
  68. package/src/codex/auth-context.ts +28 -4
  69. package/src/codex/autostart-health.ts +12 -3
  70. package/src/codex/catalog/build-entries.ts +18 -0
  71. package/src/codex/catalog/bundled.ts +155 -26
  72. package/src/codex/catalog/combo-member.ts +3 -2
  73. package/src/codex/catalog/effort.ts +26 -5
  74. package/src/codex/catalog/gather-capture.ts +5 -0
  75. package/src/codex/catalog/model-hints.ts +15 -8
  76. package/src/codex/catalog/model-visibility.ts +3 -1
  77. package/src/codex/catalog/native-models.ts +1 -1
  78. package/src/codex/catalog/operator-tiers.ts +38 -0
  79. package/src/codex/catalog/provider-models.ts +68 -35
  80. package/src/codex/catalog/retained-sync.ts +7 -5
  81. package/src/codex/catalog/routed-gather.ts +15 -1
  82. package/src/codex/catalog-auto-refresh-sources.ts +3 -4
  83. package/src/codex/catalog-auto-refresh.ts +19 -0
  84. package/src/codex/catalog.ts +1 -1
  85. package/src/codex/codex-home-owner.ts +3 -0
  86. package/src/codex/config-write-lock.ts +333 -0
  87. package/src/codex/convergence.ts +2 -1
  88. package/src/codex/desktop-app/darwin.ts +1 -1
  89. package/src/codex/desktop-app/handoff.ts +1 -1
  90. package/src/codex/desktop-app/linux.ts +1 -1
  91. package/src/codex/desktop-app/lock.ts +1 -1
  92. package/src/codex/desktop-app/types.ts +1 -1
  93. package/src/codex/desktop-app/windows.ts +1 -1
  94. package/src/codex/desktop-app-restart.ts +1 -1
  95. package/src/codex/features.ts +257 -200
  96. package/src/codex/inject/config-write-section.ts +34 -0
  97. package/src/codex/inject/multi-agent-v2.ts +14 -6
  98. package/src/codex/inject/remove.ts +36 -11
  99. package/src/codex/inject/restore.ts +85 -20
  100. package/src/codex/inject-coordination.ts +15 -6
  101. package/src/codex/inject.ts +62 -20
  102. package/src/codex/journal.ts +62 -17
  103. package/src/codex/main-account-cache.ts +43 -0
  104. package/src/codex/main-account.ts +6 -7
  105. package/src/codex/paths.ts +2 -0
  106. package/src/codex/prepared-home.ts +18 -0
  107. package/src/codex/prompt-journal.ts +25 -12
  108. package/src/codex/prompt-layers.ts +49 -26
  109. package/src/codex/prompt-lock-claim.ts +134 -0
  110. package/src/codex/prompt-lock-evidence.ts +26 -0
  111. package/src/codex/prompt-lock-io.ts +16 -0
  112. package/src/codex/prompt-lock-owner.ts +130 -0
  113. package/src/codex/prompt-lock.ts +84 -53
  114. package/src/codex/routing/failure-window.ts +146 -0
  115. package/src/codex/routing/selection.ts +4 -1
  116. package/src/codex/routing.ts +13 -17
  117. package/src/codex/runtime.ts +106 -18
  118. package/src/codex/shim.ts +45 -19
  119. package/src/combos/jev-service-exchange.ts +281 -0
  120. package/src/combos/jev.ts +34 -220
  121. package/src/config/atomic-write.ts +1 -1
  122. package/src/config/codex-ws-reuse-setting.ts +10 -0
  123. package/src/config/live-reconcile.ts +17 -0
  124. package/src/config/load-degrade.ts +8 -0
  125. package/src/config/rebase-provenance.ts +11 -0
  126. package/src/config/schema/config-schema.ts +4 -0
  127. package/src/generated/compatibility-version.json +375 -227
  128. package/src/generated/model-metadata.ts +2 -2
  129. package/src/grok/inject.ts +276 -206
  130. package/src/grok/reset-coupon-ledger.ts +223 -57
  131. package/src/grok/reset-coupons.ts +13 -6
  132. package/src/grok/sync.ts +2 -1
  133. package/src/images/loop.ts +29 -11
  134. package/src/integrations/aside-profiles.ts +10 -3
  135. package/src/integrations/catalog-refresh.ts +18 -2
  136. package/src/integrations/cursor-detect.ts +1 -1
  137. package/src/integrations/writer.ts +9 -3
  138. package/src/lib/bun-path-runtime.d.mts +32 -0
  139. package/src/lib/bun-path-runtime.mjs +95 -0
  140. package/src/lib/bun-runtime-preflight.ts +140 -0
  141. package/src/lib/desktop-cli-handoff.d.mts +20 -0
  142. package/src/lib/desktop-cli-handoff.mjs +92 -0
  143. package/src/lib/desktop-cli-record.d.mts +29 -0
  144. package/src/lib/desktop-cli-record.mjs +102 -0
  145. package/src/lib/errors.ts +25 -0
  146. package/src/lib/redact.ts +2 -2
  147. package/src/lib/request-execution-budget.ts +8 -0
  148. package/src/lib/synced-state-location.ts +46 -6
  149. package/src/lib/test-home-guard.ts +87 -1
  150. package/src/lib/translator-budget.ts +17 -4
  151. package/src/lib/windows-atomic-replace.ts +38 -16
  152. package/src/lib/windows-elevation.ts +6 -0
  153. package/src/lib/windows-secret-acl.ts +92 -25
  154. package/src/lib/winsw.ts +2 -3
  155. package/src/oauth/anthropic-account-refusal.ts +39 -1
  156. package/src/oauth/anthropic-routing.ts +1 -1
  157. package/src/oauth/anthropic-send-ownership.ts +3 -3
  158. package/src/oauth/chatgpt.ts +113 -41
  159. package/src/oauth/index.ts +7 -3
  160. package/src/oauth/meta-muse-device.ts +1 -1
  161. package/src/oauth/store.ts +1 -1
  162. package/src/oauth/types.ts +1 -1
  163. package/src/providers/anthropic-fast.ts +1 -1
  164. package/src/providers/anthropic-reset-grant-ledger.ts +1 -1
  165. package/src/providers/azure-model-metadata.ts +115 -0
  166. package/src/providers/derive.ts +21 -0
  167. package/src/providers/devin-cli-authmode-migration.ts +1 -1
  168. package/src/providers/devin-provider-merge-migration.ts +1 -1
  169. package/src/providers/github-copilot-auto.ts +169 -0
  170. package/src/providers/model-presets.ts +1 -1
  171. package/src/providers/muse-subscription-usage.ts +1 -1
  172. package/src/providers/quota/vendor-probes-key.ts +127 -17
  173. package/src/providers/quota.ts +1 -1
  174. package/src/providers/registry/entries-core.ts +6 -6
  175. package/src/providers/registry/entries-extended.ts +4 -0
  176. package/src/providers/registry/model-seeds.ts +3 -3
  177. package/src/providers/xai-fast-model.ts +1 -1
  178. package/src/responses/reasoning-envelope.ts +29 -1
  179. package/src/responses/reasoning-replay-cache.ts +17 -1
  180. package/src/responses/spill-store.ts +15 -2
  181. package/src/responses/state/metrics.ts +8 -0
  182. package/src/responses/state/spill-failure.ts +12 -0
  183. package/src/responses/state/spill-queue.ts +12 -5
  184. package/src/responses/state.ts +18 -10
  185. package/src/responses/task-input.ts +17 -0
  186. package/src/server/background-lifecycle.ts +27 -0
  187. package/src/server/chat-completions.ts +1 -0
  188. package/src/server/chat-native.ts +28 -12
  189. package/src/server/claude-messages.ts +43 -6
  190. package/src/server/inbound-body-admission.ts +25 -7
  191. package/src/server/index/claude-intercept-lifecycle.ts +3 -1
  192. package/src/server/index/link-listener.ts +30 -17
  193. package/src/server/index/serve-options.ts +18 -4
  194. package/src/server/index/spend-ledger-lifecycle.ts +2 -1
  195. package/src/server/index/startup-warnings.ts +19 -0
  196. package/src/server/index.ts +2 -1
  197. package/src/server/lifecycle.ts +31 -4
  198. package/src/server/management/agent-settings-routes.ts +93 -32
  199. package/src/server/management/codex-agent-role-routes.ts +4 -2
  200. package/src/server/management/codex-prompt-routes.ts +1 -0
  201. package/src/server/management/codex-role-auto-assign.ts +13 -2
  202. package/src/server/management/config-routes.ts +59 -8
  203. package/src/server/management/context.ts +2 -2
  204. package/src/server/management/grok-coupon-routes.ts +277 -52
  205. package/src/server/management/link-routes.ts +42 -20
  206. package/src/server/management/route-registry.ts +2 -2
  207. package/src/server/management/shared.ts +3 -3
  208. package/src/server/messages-native-oauth.ts +9 -8
  209. package/src/server/messages-native.ts +49 -13
  210. package/src/server/messages-response-headers.ts +14 -0
  211. package/src/server/relay-eager.ts +2 -0
  212. package/src/server/relay.ts +18 -7
  213. package/src/server/request-log-conversation.ts +48 -5
  214. package/src/server/request-log-failure-attribution.ts +3 -0
  215. package/src/server/request-log-terminal-status.ts +186 -0
  216. package/src/server/request-log.ts +15 -89
  217. package/src/server/responses/adapter-continuation.ts +11 -3
  218. package/src/server/responses/adapter-dispatch.ts +99 -19
  219. package/src/server/responses/codex-ws-exchange.ts +17 -2
  220. package/src/server/responses/codex-ws-pool.ts +100 -13
  221. package/src/server/responses/codex-ws-wire.ts +9 -0
  222. package/src/server/responses/combo-requested-effort.ts +3 -1
  223. package/src/server/responses/compact.ts +3 -2
  224. package/src/server/responses/core-codex-account.ts +22 -1
  225. package/src/server/responses/core-combo-native.ts +20 -3
  226. package/src/server/responses/core-combo.ts +22 -14
  227. package/src/server/responses/core-lifetime.ts +3 -36
  228. package/src/server/responses/core-opaque-recovery.ts +5 -5
  229. package/src/server/responses/core-options.ts +5 -0
  230. package/src/server/responses/core-replay.ts +73 -0
  231. package/src/server/responses/core.ts +9 -9
  232. package/src/server/responses/empty-completion-guard.ts +18 -2
  233. package/src/server/responses/fetch-helpers.ts +26 -3
  234. package/src/server/responses/jev-model-invoke.ts +1 -1
  235. package/src/server/responses/passthrough-delivery.ts +24 -2
  236. package/src/server/responses/passthrough-dispatch.ts +134 -62
  237. package/src/server/responses/passthrough-execution.ts +3 -3
  238. package/src/server/responses/request-prepare.ts +4 -0
  239. package/src/server/responses/request-send-budget.ts +112 -49
  240. package/src/server/responses/request-transport.ts +125 -5
  241. package/src/server/responses/response-effects.ts +1 -0
  242. package/src/server/responses/run-turn-execution.ts +15 -5
  243. package/src/server/responses/sidecar-execution.ts +30 -16
  244. package/src/server/responses/sidecar-send-budget.ts +58 -0
  245. package/src/server/responses/spend-storage-error.ts +33 -0
  246. package/src/server/responses/ws-upstream.ts +19 -6
  247. package/src/server/startup-health-cache.ts +2 -1
  248. package/src/server/workflow-refusal.ts +3 -0
  249. package/src/service/cli.ts +20 -4
  250. package/src/service/desktop-command-guard.ts +32 -0
  251. package/src/service/desktop-startup.ts +66 -40
  252. package/src/service/desktop-supervision.d.mts +26 -0
  253. package/src/service/desktop-supervision.mjs +140 -0
  254. package/src/service/managing-cli.ts +1 -0
  255. package/src/service/orchestration.ts +46 -20
  256. package/src/service/repair.ts +25 -9
  257. package/src/service/state.ts +4 -3
  258. package/src/service/systemd.ts +17 -8
  259. package/src/service/windows-ops.ts +61 -22
  260. package/src/storage/policy-job.ts +5 -1
  261. package/src/storage/policy-worker.ts +3 -1
  262. package/src/storage/policy.ts +9 -0
  263. package/src/types/config.ts +21 -0
  264. package/src/types/request.ts +10 -0
  265. package/src/types/wire.ts +1 -0
  266. package/src/update/index.ts +57 -9
  267. package/src/update/restart-ownership.ts +10 -3
  268. package/src/update/runtime-ownership.d.mts +6 -0
  269. package/src/update/runtime-ownership.mjs +23 -7
  270. package/src/usage/expected-prices.ts +1 -1
  271. package/src/web-search/loop.ts +5 -2
  272. package/gui/dist/assets/App-7yhoys8a.js +0 -54
@@ -0,0 +1,186 @@
1
+ import {
2
+ CYBER_POLICY_ERROR_CODE,
3
+ httpStatusFromTerminalError,
4
+ isCyberPolicyCode,
5
+ isCyberPolicyMessage,
6
+ isRateLimitOrQuotaFailureMessage,
7
+ recognizedUpstreamError,
8
+ } from "../lib/errors";
9
+ import { redactSecretString } from "../lib/redact";
10
+
11
+ /** First bounded upstream diagnostics shared by request contexts and final rows. */
12
+ export interface UpstreamErrorDiagnostics {
13
+ upstreamErrorCode?: string;
14
+ upstreamErrorType?: string;
15
+ upstreamRequestId?: string;
16
+ }
17
+
18
+ interface TerminalStatusContext extends UpstreamErrorDiagnostics {
19
+ terminalHttpStatus?: number;
20
+ terminalErrorCode?: typeof CYBER_POLICY_ERROR_CODE;
21
+ }
22
+
23
+ export interface UpstreamErrorPayload {
24
+ type?: unknown;
25
+ code?: unknown;
26
+ message?: unknown;
27
+ error?: { type?: unknown; code?: unknown; message?: unknown };
28
+ last_error?: { type?: unknown; code?: unknown; message?: unknown };
29
+ response?: {
30
+ error?: { type?: unknown; code?: unknown; message?: unknown };
31
+ incomplete_details?: { code?: unknown; message?: unknown; reason?: unknown };
32
+ };
33
+ }
34
+
35
+ // Bare error evidence is provisional: only a genuine terminal owns account-health status.
36
+ const provisionalStatuses = new WeakMap<TerminalStatusContext, number>();
37
+ const genuineTerminals = new WeakSet<TerminalStatusContext>();
38
+
39
+ const UPSTREAM_DIAGNOSTIC_TOKEN = /^[A-Za-z0-9_.:-]{1,128}$/;
40
+
41
+ /**
42
+ * Upstream diagnostics are provider-controlled. The token alphabet alone also admits
43
+ * credential formats (an echoed `sk-...` key fits it), so a value the shared redactor
44
+ * would change is dropped rather than recorded, warned, or projected into log rows.
45
+ */
46
+ function isSafeDiagnosticToken(value: unknown): value is string {
47
+ return typeof value === "string" && UPSTREAM_DIAGNOSTIC_TOKEN.test(value) && redactSecretString(value) === value;
48
+ }
49
+
50
+ /**
51
+ * Error classes published by the supported provider APIs (OpenAI/Responses, Anthropic, Gemini).
52
+ * The type is diagnostic only, so the vocabulary is closed: a gateway can echo anything in
53
+ * `error.type`, including a configured credential whose format no redactor recognizes, and a
54
+ * value outside this set is simply not recorded, which is what 2.82.0 did for every type.
55
+ */
56
+ const KNOWN_UPSTREAM_ERROR_TYPES: ReadonlySet<string> = new Set([
57
+ "api_error", "authentication_error", "billing_error", "insufficient_quota", "invalid_request_error",
58
+ "not_found_error", "overloaded_error", "permission_error", "rate_limit_error", "request_too_large",
59
+ "requests", "server_error", "timeout_error", "tokens",
60
+ "INVALID_ARGUMENT", "FAILED_PRECONDITION", "PERMISSION_DENIED", "UNAUTHENTICATED", "NOT_FOUND",
61
+ "RESOURCE_EXHAUSTED", "INTERNAL", "UNAVAILABLE", "DEADLINE_EXCEEDED",
62
+ ]);
63
+
64
+ /** Bare top-level error codes worth keeping: the classes recognizedUpstreamError maps, plus server_error. */
65
+ const KNOWN_BARE_ERROR_CODES: ReadonlySet<string> = new Set([
66
+ "rate_limit_exceeded", "rate_limit_error", "server_is_overloaded", "overloaded_error", "server_error",
67
+ ]);
68
+
69
+ function noteBoundedDiagnostic(
70
+ logCtx: TerminalStatusContext,
71
+ field: "upstreamErrorCode" | "upstreamErrorType" | "upstreamRequestId",
72
+ value: unknown,
73
+ ): void {
74
+ if (logCtx[field] !== undefined) return;
75
+ if (!isSafeDiagnosticToken(value)) return;
76
+ if (field === "upstreamErrorType" && !KNOWN_UPSTREAM_ERROR_TYPES.has(value)) return;
77
+ logCtx[field] = value;
78
+ const status = logCtx.terminalHttpStatus;
79
+ if (status === undefined || status >= 500) console.warn(`[opencodex] upstream failure${status ? ` status=${status}` : ""}${logCtx.upstreamErrorCode ? ` code=${logCtx.upstreamErrorCode}` : ""}${logCtx.upstreamRequestId ? ` request_id=${logCtx.upstreamRequestId}` : ""}`);
80
+ }
81
+
82
+ export function noteUpstreamRequestId(logCtx: TerminalStatusContext, headers: Headers): void {
83
+ noteBoundedDiagnostic(logCtx, "upstreamRequestId", headers.get("openai-request-id") ?? headers.get("x-request-id"));
84
+ }
85
+
86
+ export function captureUpstreamTerminalDiagnostics(logCtx: TerminalStatusContext, json: UpstreamErrorPayload): void {
87
+ captureTerminalHttpStatus(logCtx, json);
88
+ noteBoundedDiagnostic(logCtx, "upstreamErrorCode",
89
+ json.error?.code ?? json.last_error?.code ?? json.response?.error?.code
90
+ // A bare top-level code (new in 2.83.0) has no envelope contract, so only a known class is kept.
91
+ ?? (typeof json.code === "string" && KNOWN_BARE_ERROR_CODES.has(json.code) ? json.code : undefined));
92
+ noteBoundedDiagnostic(logCtx, "upstreamErrorType",
93
+ json.error?.type ?? json.last_error?.type ?? json.response?.error?.type);
94
+ }
95
+
96
+ export function upstreamDiagnosticLogFields(logCtx: UpstreamErrorDiagnostics): UpstreamErrorDiagnostics {
97
+ return {
98
+ ...(logCtx.upstreamErrorCode ? { upstreamErrorCode: logCtx.upstreamErrorCode } : {}),
99
+ ...(logCtx.upstreamErrorType ? { upstreamErrorType: logCtx.upstreamErrorType } : {}),
100
+ ...(logCtx.upstreamRequestId ? { upstreamRequestId: logCtx.upstreamRequestId } : {}),
101
+ };
102
+ }
103
+
104
+ function captureTerminalHttpStatus(
105
+ logCtx: TerminalStatusContext,
106
+ json: UpstreamErrorPayload,
107
+ ): void {
108
+ const type = json.type;
109
+ const terminal = type === "response.failed" || type === "response.completed" || type === "response.incomplete";
110
+ if (!terminal && type !== "error") return;
111
+ if (terminal && provisionalStatuses.has(logCtx)) {
112
+ const provisional = provisionalStatuses.get(logCtx);
113
+ provisionalStatuses.delete(logCtx);
114
+ if (logCtx.terminalHttpStatus === provisional) {
115
+ delete logCtx.terminalHttpStatus;
116
+ delete logCtx.terminalErrorCode;
117
+ }
118
+ }
119
+ if (logCtx.terminalHttpStatus !== undefined || (type === "error" && genuineTerminals.has(logCtx))) return;
120
+ if (terminal) genuineTerminals.add(logCtx);
121
+ const noteStatus = (status: number): void => {
122
+ logCtx.terminalHttpStatus = status;
123
+ if (type === "error") provisionalStatuses.set(logCtx, status);
124
+ };
125
+ // A completed terminal only retires provisional bare-error evidence; it records no status.
126
+ if (type === "response.completed") return;
127
+ const responseError = json.response?.error;
128
+ const responseDetails = json.response?.incomplete_details;
129
+ const candidates: Array<{ type?: unknown; code?: unknown; message?: unknown } | undefined> = [
130
+ json.error, json.last_error, responseError, responseDetails, json,
131
+ ];
132
+ const policy = candidates.some(candidate => (
133
+ candidate?.code === null || typeof candidate?.code === "string"
134
+ ) && isCyberPolicyCode(candidate.code as string | null | undefined))
135
+ || candidates.some(candidate => (
136
+ typeof candidate?.message === "string"
137
+ && candidate.message.trim().length > 0
138
+ && isCyberPolicyMessage(candidate.message)
139
+ ));
140
+ if (policy) {
141
+ logCtx.terminalErrorCode = CYBER_POLICY_ERROR_CODE;
142
+ noteStatus(400);
143
+ return;
144
+ }
145
+ // A quota terminal can carry only a structured reason, without an error message.
146
+ // Keep this separate from normal output limits and from the policy precedence above.
147
+ const quotaTag = (value: unknown): boolean => value === "usage_limit_reached"
148
+ || value === "rate_limit_exceeded" || value === "insufficient_quota";
149
+ const structuredRefusal = candidates.some(candidate => [400, 401, 403, 499].includes(
150
+ httpStatusFromTerminalError({
151
+ type: typeof candidate?.type === "string" ? candidate.type : undefined,
152
+ code: typeof candidate?.code === "string" ? candidate.code : undefined,
153
+ }),
154
+ ));
155
+ const ordinaryIncompleteReason = typeof responseDetails?.reason === "string"
156
+ && ["max_output_tokens", "content_filter", "steered", "upstream_stall_timeout", "adapter_eof"].includes(responseDetails.reason);
157
+ if (type === "response.incomplete" && !structuredRefusal && (quotaTag(responseDetails?.reason) || candidates.some(candidate =>
158
+ quotaTag(candidate?.code)
159
+ || quotaTag(candidate?.type) || candidate?.type === "rate_limit_error"
160
+ || (!ordinaryIncompleteReason && typeof candidate?.message === "string" && isRateLimitOrQuotaFailureMessage(candidate.message))
161
+ ))) {
162
+ // The shared quota classifier also accepts a numeric HTTP status as its message.
163
+ // Preserve explicit payment-required evidence rather than relabeling it as 429.
164
+ noteStatus(candidates.some(candidate => typeof candidate?.message === "string"
165
+ && Number(candidate.message.trim()) === 402) ? 402 : 429);
166
+ return;
167
+ }
168
+ if (type === "error") {
169
+ const structured = recognizedUpstreamError({
170
+ code: candidates.find(candidate => typeof candidate?.code === "string")?.code,
171
+ type: candidates.slice(0, -1).find(candidate => typeof candidate?.type === "string")?.type,
172
+ });
173
+ // Unrecognized bare errors leave the status to explicit event fields (combo preflight).
174
+ if (structured) noteStatus(structured.httpStatus);
175
+ return;
176
+ }
177
+ if (type !== "response.failed" || !responseError || typeof responseError !== "object") return;
178
+ const responseCode = responseError.code === null || typeof responseError.code === "string"
179
+ ? responseError.code
180
+ : undefined;
181
+ noteStatus(httpStatusFromTerminalError({
182
+ type: typeof responseError.type === "string" ? responseError.type : undefined,
183
+ code: responseCode,
184
+ message: typeof responseError.message === "string" ? responseError.message : undefined,
185
+ }));
186
+ }
@@ -4,6 +4,8 @@ import { stampApiKeyAccountLabel, usesApiKeyAccount } from "../providers/label";
4
4
  import { KEY_ACCOUNT_LOG_LABEL_RE } from "../codex/account-label";
5
5
  import { attemptAccountChanged, sealRequestAttemptIdentity } from "./request-log-account-rotation";
6
6
  export { sealRequestAttemptIdentity };
7
+ import { captureUpstreamTerminalDiagnostics, upstreamDiagnosticLogFields, type UpstreamErrorDiagnostics, type UpstreamErrorPayload } from "./request-log-terminal-status";
8
+ export { noteUpstreamRequestId } from "./request-log-terminal-status";
7
9
  import { readBoundedResponseBody } from "../lib/bounded-body";
8
10
  import type { ResponsesTerminalStatus } from "../bridge";
9
11
  import {
@@ -12,8 +14,6 @@ import {
12
14
  httpStatusFromTerminalError as httpStatusFromClassifiedTerminalError,
13
15
  isClientClosedMessage,
14
16
  isCyberPolicyCode,
15
- isCyberPolicyMessage,
16
- isRateLimitOrQuotaFailureMessage,
17
17
  isUpstreamResetReplayRefusedMessage,
18
18
  upstreamErrorMessageFromPayload,
19
19
  } from "../lib/errors";
@@ -104,7 +104,7 @@ function cacheDiagnosticHooks(): CacheDiagnosticHooks | undefined {
104
104
  return (globalThis as Record<symbol, CacheDiagnosticHooks | undefined>)[CACHE_DIAGNOSTIC_HOOK];
105
105
  }
106
106
 
107
- export interface RequestLogContext {
107
+ export interface RequestLogContext extends UpstreamErrorDiagnostics {
108
108
  model: string;
109
109
  provider: string;
110
110
  /** Optional process-lifetime aggregate sink, injected by the server composition owner. */
@@ -254,6 +254,8 @@ export interface RequestLogContext {
254
254
  conversationStateScrub?: "account-change";
255
255
  transportPhase?: "pre_headers" | "mid_stream" | "terminal_sse";
256
256
  terminalSource?: "upstream" | "synthetic";
257
+ /** Closed failure evidence supplied by the transport, never inferred from provider text. */
258
+ causeHint?: RequestFailureCause;
257
259
  /** Bounded route-decision trace (RI-01); never contains secrets. */
258
260
  routeDecision?: RouteDecisionTraceV1;
259
261
  /** Full eligible provider/model membership from a policy route; never logged. */
@@ -282,7 +284,7 @@ export function rebindCacheDiagnosticBody(
282
284
  cacheDiagnosticHooks()?.rebind(body, draft);
283
285
  }
284
286
 
285
- export interface RequestLogEntry {
287
+ export interface RequestLogEntry extends UpstreamErrorDiagnostics {
286
288
  requestId: string;
287
289
  /** The logical request this row belongs to (#4546); absent on rows written without a budget. */
288
290
  logicalRequestId?: string;
@@ -840,6 +842,7 @@ export function requestLogErrorCode(
840
842
  status: number,
841
843
  upstreamError?: string,
842
844
  terminalErrorCode?: string,
845
+ replayRefusal = false,
843
846
  ): string | undefined {
844
847
  if (status >= 200 && status < 400) return undefined;
845
848
  // A structured terminal code is authoritative even when the provider message is localized,
@@ -868,10 +871,8 @@ export function requestLogErrorCode(
868
871
  return "permission_denied";
869
872
  }
870
873
  if (status === 429) {
871
- // A refused ambiguous reset answers 429 by design (it must not invite a client
872
- // retry that could duplicate inference); classify it by its message so the log
873
- // distinguishes a proxy refusal from provider throttling.
874
- if (upstreamError?.trim() && isUpstreamResetReplayRefusedMessage(upstreamError)) {
874
+ // Prefer transport provenance; retain the legacy message fallback for older callers.
875
+ if (replayRefusal || (upstreamError?.trim() && isUpstreamResetReplayRefusedMessage(upstreamError))) {
875
876
  return UPSTREAM_RESET_REPLAY_REFUSED_CODE;
876
877
  }
877
878
  return "rate_limit_exceeded";
@@ -1112,16 +1113,8 @@ function captureUpstreamErrorParsed(
1112
1113
  parsed: unknown | undefined,
1113
1114
  ): void {
1114
1115
  if (parsed !== undefined && parsed !== null) {
1115
- const json = parsed as {
1116
- type?: unknown;
1117
- error?: { message?: unknown };
1118
- last_error?: { message?: unknown };
1119
- response?: {
1120
- error?: { type?: unknown; code?: unknown; message?: unknown };
1121
- incomplete_details?: { reason?: unknown; message?: unknown };
1122
- };
1123
- };
1124
- captureTerminalHttpStatus(logCtx, json);
1116
+ const json = parsed as UpstreamErrorPayload;
1117
+ captureUpstreamTerminalDiagnostics(logCtx, json);
1125
1118
  const reason = json?.response?.incomplete_details?.reason;
1126
1119
  if (json.type === "response.incomplete"
1127
1120
  && logCtx.terminalIncompleteReason === undefined
@@ -1165,75 +1158,6 @@ function incompleteReasonLabel(reason: string): string {
1165
1158
  }
1166
1159
  }
1167
1160
 
1168
- function captureTerminalHttpStatus(
1169
- logCtx: RequestLogContext,
1170
- json: {
1171
- type?: unknown;
1172
- code?: unknown;
1173
- message?: unknown;
1174
- error?: { type?: unknown; code?: unknown; message?: unknown };
1175
- last_error?: { type?: unknown; code?: unknown; message?: unknown };
1176
- response?: {
1177
- error?: { type?: unknown; code?: unknown; message?: unknown };
1178
- incomplete_details?: { code?: unknown; message?: unknown; reason?: unknown };
1179
- };
1180
- },
1181
- ): void {
1182
- if (logCtx.terminalHttpStatus !== undefined) return;
1183
- const type = json.type;
1184
- if (type !== "response.failed" && type !== "response.incomplete" && type !== "error") return;
1185
- const responseError = json.response?.error;
1186
- const responseDetails = json.response?.incomplete_details;
1187
- const candidates: Array<{ type?: unknown; code?: unknown; message?: unknown } | undefined> = [
1188
- json.error, json.last_error, responseError, responseDetails, json,
1189
- ];
1190
- const policy = candidates.some(candidate => (
1191
- candidate?.code === null || typeof candidate?.code === "string"
1192
- ) && isCyberPolicyCode(candidate.code as string | null | undefined))
1193
- || candidates.some(candidate => (
1194
- typeof candidate?.message === "string"
1195
- && candidate.message.trim().length > 0
1196
- && isCyberPolicyMessage(candidate.message)
1197
- ));
1198
- if (policy) {
1199
- logCtx.terminalErrorCode = CYBER_POLICY_ERROR_CODE;
1200
- logCtx.terminalHttpStatus = 400;
1201
- return;
1202
- }
1203
- // A quota terminal can carry only a structured reason, without an error message.
1204
- // Keep this separate from normal output limits and from the policy precedence above.
1205
- const quotaTag = (value: unknown): boolean => value === "usage_limit_reached"
1206
- || value === "rate_limit_exceeded" || value === "insufficient_quota";
1207
- const structuredRefusal = candidates.some(candidate => [400, 401, 403, 499].includes(
1208
- httpStatusFromTerminalError({
1209
- type: typeof candidate?.type === "string" ? candidate.type : undefined,
1210
- code: typeof candidate?.code === "string" ? candidate.code : undefined,
1211
- }),
1212
- ));
1213
- const ordinaryIncompleteReason = typeof responseDetails?.reason === "string"
1214
- && ["max_output_tokens", "content_filter", "steered", "upstream_stall_timeout", "adapter_eof"].includes(responseDetails.reason);
1215
- if (type === "response.incomplete" && !structuredRefusal && (quotaTag(responseDetails?.reason) || candidates.some(candidate =>
1216
- quotaTag(candidate?.code)
1217
- || quotaTag(candidate?.type) || candidate?.type === "rate_limit_error"
1218
- || (!ordinaryIncompleteReason && typeof candidate?.message === "string" && isRateLimitOrQuotaFailureMessage(candidate.message))
1219
- ))) {
1220
- // The shared quota classifier also accepts a numeric HTTP status as its message.
1221
- // Preserve explicit payment-required evidence rather than relabeling it as 429.
1222
- logCtx.terminalHttpStatus = candidates.some(candidate => typeof candidate?.message === "string"
1223
- && Number(candidate.message.trim()) === 402) ? 402 : 429;
1224
- return;
1225
- }
1226
- if (type !== "response.failed" || !responseError || typeof responseError !== "object") return;
1227
- const responseCode = responseError.code === null || typeof responseError.code === "string"
1228
- ? responseError.code
1229
- : undefined;
1230
- logCtx.terminalHttpStatus = httpStatusFromTerminalError({
1231
- type: typeof responseError.type === "string" ? responseError.type : undefined,
1232
- code: responseCode,
1233
- message: typeof responseError.message === "string" ? responseError.message : undefined,
1234
- });
1235
- }
1236
-
1237
1161
  /** Map a terminal Responses error object to the HTTP status we record in /api/logs. */
1238
1162
  export function httpStatusFromTerminalError(error: {
1239
1163
  type?: string;
@@ -1440,12 +1364,12 @@ export function addFinalRequestLog(
1440
1364
  const effectiveStatus = status >= 500 && logCtx.upstreamError && isClientClosedMessage(logCtx.upstreamError)
1441
1365
  ? 499
1442
1366
  : status;
1443
- // A locally assigned code wins: it names a refusal this proxy made itself, which no
1444
- // status-plus-upstream-message classification can reconstruct.
1367
+ // Locally assigned codes name proxy refusals that status and upstream text cannot reconstruct.
1445
1368
  const errorCode = logCtx.errorCode ?? requestLogErrorCode(
1446
1369
  effectiveStatus,
1447
1370
  logCtx.upstreamError,
1448
1371
  logCtx.terminalErrorCode,
1372
+ logCtx.causeHint === "transport-ambiguous" && logCtx.terminalSource === "synthetic",
1449
1373
  );
1450
1374
  // A response.failed whose classified status is 499 is still a client cancel, not an upstream
1451
1375
  // terminal failure — keep /api/logs closeReason aligned with that.
@@ -1475,6 +1399,7 @@ export function addFinalRequestLog(
1475
1399
  ...(logCtx.transportPhase ? { transportPhase: logCtx.transportPhase } : {}),
1476
1400
  ...(logCtx.terminalSource ? { terminalSource: logCtx.terminalSource } : {}),
1477
1401
  outputObserved: logCtx.firstOutputMs !== undefined,
1402
+ ...(logCtx.causeHint ? { causeHint: logCtx.causeHint } : {}),
1478
1403
  locallyAnswered: logCtx.localTerminalReason !== undefined,
1479
1404
  ...(logCtx.activeAttempt ? { attempt: logCtx.activeAttempt } : {}),
1480
1405
  });
@@ -1593,6 +1518,7 @@ export function addFinalRequestLog(
1593
1518
  ...(meta?.terminalStatus ? { terminalStatus: meta.terminalStatus } : {}),
1594
1519
  ...(closeReason ? { closeReason } : {}),
1595
1520
  ...(logCtx.upstreamError ? { upstreamError: logCtx.upstreamError } : {}),
1521
+ ...upstreamDiagnosticLogFields(logCtx),
1596
1522
  usageStatus,
1597
1523
  ...(loggedUsage ? { usage: loggedUsage } : {}),
1598
1524
  ...(totalTokens !== undefined ? { totalTokens } : {}),
@@ -83,6 +83,7 @@ export function createAdapterContinuations(
83
83
  | "oauthDispatch"
84
84
  | "invalidateSameTargetRequest"
85
85
  | "resolveSelectionAdapter"
86
+ | "resolveCopilotSelection"
86
87
  | "anthropicInstance"
87
88
  | "currentAnthropicRouteDecision"
88
89
  | "anthropicRouteDecision"
@@ -156,7 +157,8 @@ export function createAdapterContinuations(
156
157
  /**
157
158
  * One bounded internal re-ask for Anthropic end_turn-without-tool-call turns. Replays the
158
159
  * continuation on a 429 with the same-key retry budget (hoisted per request), then falls
159
- * back to key/account failover; a failure becomes an in-stream adapter error so the client
160
+ * back to key/account failover, rebuilding Copilot model, wire, and credential negotiation.
161
+ * A failure becomes an in-stream adapter error so the client
160
162
  * never sees a second hidden HTTP response or an unbounded retry loop.
161
163
  */
162
164
  const fetchTerminalGuardContinuation = async function* (
@@ -377,6 +379,12 @@ export function createAdapterContinuations(
377
379
  if (rotated) {
378
380
  try { void response.body?.cancel().catch(() => {}); } catch { /* already closed */ }
379
381
  route.provider = rotated;
382
+ try { await transportState.resolveCopilotSelection(nextParsed); }
383
+ catch (error) {
384
+ yield { type: "error", message: `Provider continuation failed: ${redactSecretString(error instanceof Error ? error.message : String(error))}`,
385
+ ...(options.abortSignal?.aborted || upstream.signal.aborted ? { status: 499 } : {}) };
386
+ return;
387
+ }
380
388
  invalidateSameTargetRequest();
381
389
  transportState.activeAdapter = resolveSelectionAdapter(
382
390
  resolveWireProtocolOverride(route.providerName, route.modelId, route.provider, inboundWire, route.staticPolicy),
@@ -401,7 +409,7 @@ export function createAdapterContinuations(
401
409
  }
402
410
  }
403
411
  if (
404
- (response.status === 429 || response.status === 403)
412
+ (response.status === 429 || response.status === 403 || response.status === 401)
405
413
  && anthropicInstance
406
414
  && transportState.anthropicPoolAccountId
407
415
  && !isNonReplayableResponse(response)
@@ -428,7 +436,7 @@ export function createAdapterContinuations(
428
436
  );
429
437
  sealRequestAttemptIdentity(logCtx.activeAttempt, logCtx.provider, transportState.activeAdapter.name, logCtx.accountLogLabel);
430
438
  recordAttemptCredentialSource(logCtx.activeAttempt, route.providerName, route.provider, transportState.activeAdapter.name);
431
- nextContinuationRecoveryKind = "anthropic-oauth-429";
439
+ nextContinuationRecoveryKind = response.status === 401 ? "oauth-401" : "anthropic-oauth-429";
432
440
  continue;
433
441
  } catch {
434
442
  // fall through to emit continuation error below