@bitkyc08/opencodex 2.52.0 → 2.53.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 (236) hide show
  1. package/gui/dist/assets/index-BBOZWGB6.css +1 -0
  2. package/gui/dist/assets/index-BlO4Yl6q.js +128 -0
  3. package/gui/dist/index.html +2 -2
  4. package/native/remote-workspace-helper/Cargo.lock +130 -0
  5. package/native/remote-workspace-helper/Cargo.toml +24 -0
  6. package/native/remote-workspace-helper/src/main.rs +49 -0
  7. package/native/remote-workspace-helper/src/protocol.rs +246 -0
  8. package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
  9. package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
  10. package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
  11. package/package.json +6 -1
  12. package/src/adapters/anthropic-image-normalize.ts +30 -2
  13. package/src/adapters/anthropic.ts +1 -1
  14. package/src/adapters/base.ts +8 -2
  15. package/src/adapters/cursor/cursor-errors.ts +12 -0
  16. package/src/adapters/cursor/thread-continuity.ts +93 -0
  17. package/src/adapters/cursor.ts +104 -73
  18. package/src/adapters/devin/cloud-direct/chat.ts +312 -23
  19. package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
  20. package/src/adapters/devin/live-models.ts +70 -3
  21. package/src/adapters/devin.ts +281 -21
  22. package/src/adapters/google-wire-compiler.ts +14 -6
  23. package/src/adapters/google.ts +22 -8
  24. package/src/adapters/kiro/adapter.ts +316 -0
  25. package/src/adapters/kiro/conversation.ts +136 -0
  26. package/src/adapters/kiro/payload.ts +432 -0
  27. package/src/adapters/kiro/reasoning.ts +56 -0
  28. package/src/adapters/kiro/stream.ts +1153 -0
  29. package/src/adapters/kiro/usage.ts +223 -0
  30. package/src/adapters/kiro/wire.ts +76 -0
  31. package/src/adapters/kiro.ts +8 -2319
  32. package/src/adapters/mimo-free.ts +1 -1
  33. package/src/adapters/openai-chat-images.ts +101 -0
  34. package/src/adapters/openai-chat.ts +201 -181
  35. package/src/adapters/openai-responses.ts +92 -224
  36. package/src/adapters/registry.ts +0 -7
  37. package/src/adapters/run-turn-queue.ts +13 -6
  38. package/src/bridge.ts +14 -15
  39. package/src/chat/inbound.ts +29 -4
  40. package/src/chat/outbound.ts +145 -107
  41. package/src/claude/desktop-profile.ts +4 -6
  42. package/src/cli/account-api.ts +14 -0
  43. package/src/cli/account-extended.ts +1 -1
  44. package/src/cli/account-history.ts +60 -0
  45. package/src/cli/account-main.ts +80 -0
  46. package/src/cli/account.ts +11 -3
  47. package/src/cli/capabilities.ts +113 -0
  48. package/src/cli/catalog.ts +109 -0
  49. package/src/cli/dispatch.ts +9 -0
  50. package/src/cli/help.ts +2 -0
  51. package/src/cli/index.ts +2 -2
  52. package/src/cli/observe.ts +28 -1
  53. package/src/cli/opencode.ts +42 -8
  54. package/src/cli/provider-runtime.ts +11 -1
  55. package/src/cli/provider.ts +22 -2
  56. package/src/cli/registry.ts +21 -0
  57. package/src/cli/remote-workspace.ts +154 -0
  58. package/src/cli/status.ts +39 -7
  59. package/src/cli/usage-report.ts +14 -2
  60. package/src/client/hub-client.ts +34 -0
  61. package/src/client/hub-state.ts +9 -1
  62. package/src/codex/account-store.ts +78 -0
  63. package/src/codex/auth-api.ts +81 -54
  64. package/src/codex/auth-context.ts +45 -16
  65. package/src/codex/catalog/effort.ts +1 -1
  66. package/src/codex/catalog/metadata.ts +3 -6
  67. package/src/codex/catalog/native-models.ts +4 -4
  68. package/src/codex/catalog/parsing.ts +2 -20
  69. package/src/codex/catalog/provider-fetch.ts +10 -1
  70. package/src/codex/catalog/remote.ts +233 -0
  71. package/src/codex/catalog/sync.ts +403 -35
  72. package/src/codex/convergence.ts +1 -1
  73. package/src/codex/history-manifest.ts +36 -0
  74. package/src/codex/history-provider.ts +32 -5
  75. package/src/codex/inject.ts +9 -0
  76. package/src/codex/main-account.ts +113 -0
  77. package/src/codex/main-device-reauth-api.ts +89 -0
  78. package/src/codex/main-device-reauth.ts +217 -0
  79. package/src/codex/native-residue.ts +9 -2
  80. package/src/codex/quota-auto-refresh.ts +3 -2
  81. package/src/codex/quota-capacity.ts +98 -0
  82. package/src/codex/quota-history.ts +160 -0
  83. package/src/codex/quota-types.ts +8 -0
  84. package/src/codex/quota.ts +118 -91
  85. package/src/codex/refresh.ts +2 -1
  86. package/src/codex/routing.ts +90 -17
  87. package/src/codex/sync.ts +33 -4
  88. package/src/combos/request.ts +19 -1
  89. package/src/config/multi-agent-surface.ts +61 -0
  90. package/src/config/provider-validation.ts +176 -0
  91. package/src/config.ts +213 -11
  92. package/src/generated/compatibility-version.json +436 -168
  93. package/src/images/loop.ts +119 -36
  94. package/src/lib/admission.ts +12 -6
  95. package/src/lib/redact.ts +7 -0
  96. package/src/lib/translator-budget.ts +4 -3
  97. package/src/lib/windows-atomic-replace.ts +1 -0
  98. package/src/lib/windows-elevation.ts +1 -1
  99. package/src/oauth/chatgpt-device.ts +62 -5
  100. package/src/oauth/devin/cli-import.ts +130 -0
  101. package/src/oauth/devin.ts +63 -8
  102. package/src/oauth/index.ts +29 -14
  103. package/src/oauth/kiro.ts +18 -6
  104. package/src/oauth/login-cli.ts +9 -1
  105. package/src/oauth/meta-muse-device.ts +464 -0
  106. package/src/oauth/meta-muse.ts +123 -32
  107. package/src/oauth/pool-kernel.ts +9 -0
  108. package/src/oauth/pool-settings-capability.ts +2 -2
  109. package/src/oauth/store.ts +57 -0
  110. package/src/oauth/types.ts +31 -0
  111. package/src/providers/derive.ts +13 -3
  112. package/src/providers/devin-cli-authmode-migration.ts +57 -35
  113. package/src/providers/devin-provider-merge-migration.ts +240 -0
  114. package/src/providers/muse-key-quota.ts +117 -0
  115. package/src/providers/muse-subscription-usage.ts +14 -2
  116. package/src/providers/openai-sidecar.ts +25 -3
  117. package/src/providers/opencode-zen-rate-limit.ts +58 -0
  118. package/src/providers/provider-id-rewrite.ts +20 -5
  119. package/src/providers/quota-types.ts +12 -0
  120. package/src/providers/quota.ts +143 -102
  121. package/src/providers/reasoning-metadata.ts +543 -0
  122. package/src/providers/registry.ts +80 -49
  123. package/src/reasoning-effort.ts +26 -2
  124. package/src/remote/hub-usage.ts +32 -0
  125. package/src/remote-control/index.ts +192 -41
  126. package/src/remote-control/workspace-activation.ts +9 -0
  127. package/src/remote-control/workspace-agent-connection.ts +366 -0
  128. package/src/remote-control/workspace-claude-runtime.ts +243 -0
  129. package/src/remote-control/workspace-codex-runtime.ts +531 -0
  130. package/src/remote-control/workspace-codex-sandbox.ts +115 -0
  131. package/src/remote-control/workspace-command-runner.ts +748 -0
  132. package/src/remote-control/workspace-coordinator.ts +231 -0
  133. package/src/remote-control/workspace-device.ts +585 -0
  134. package/src/remote-control/workspace-executable.ts +43 -0
  135. package/src/remote-control/workspace-executor.ts +397 -0
  136. package/src/remote-control/workspace-hub.ts +519 -0
  137. package/src/remote-control/workspace-pi-runtime.ts +382 -0
  138. package/src/remote-control/workspace-process.ts +129 -0
  139. package/src/remote-control/workspace-rpc.ts +304 -0
  140. package/src/remote-control/workspace-runtime.ts +60 -0
  141. package/src/remote-control/workspace-secret-store.ts +39 -0
  142. package/src/remote-control/workspace-sessions.ts +799 -0
  143. package/src/remote-control/workspace-tool-bridge.ts +192 -0
  144. package/src/responses/code-mode-helper-compat.ts +22 -3
  145. package/src/responses/hosted-tool-policy.ts +0 -1
  146. package/src/responses/muse-tool-name-alias.ts +379 -0
  147. package/src/responses/plaintext-v2-agent-messages.ts +902 -0
  148. package/src/router.ts +7 -0
  149. package/src/routing/compatibility/behavior.ts +0 -1
  150. package/src/server/audio-client.ts +64 -0
  151. package/src/server/audio-dictation.ts +91 -0
  152. package/src/server/audio-live.ts +185 -0
  153. package/src/server/audio-transcriptions.ts +183 -0
  154. package/src/server/audio-upstream.ts +153 -0
  155. package/src/server/auth-cors.ts +61 -2
  156. package/src/server/chat-completions.ts +1 -1
  157. package/src/server/chat-native-sse.ts +92 -48
  158. package/src/server/chat-native.ts +37 -15
  159. package/src/server/hub-usage.ts +57 -0
  160. package/src/server/images.ts +4 -0
  161. package/src/server/index.ts +722 -57
  162. package/src/server/lifecycle.ts +5 -6
  163. package/src/server/live-call-bindings.ts +60 -0
  164. package/src/server/live.ts +12 -1
  165. package/src/server/management/agent-settings-routes.ts +25 -4
  166. package/src/server/management/api-access.ts +37 -0
  167. package/src/server/management/api-key-usage.ts +7 -2
  168. package/src/server/management/config-routes.ts +1 -18
  169. package/src/server/management/context.ts +15 -0
  170. package/src/server/management/logs-usage-routes.ts +2 -0
  171. package/src/server/management/oauth-account-routes.ts +39 -12
  172. package/src/server/management/provider-routes.ts +125 -2
  173. package/src/server/management/remote-workspace-routes.ts +140 -0
  174. package/src/server/management/route-registry.ts +15 -0
  175. package/src/server/management/usage-aggregate-cache.ts +14 -15
  176. package/src/server/management/usage-summary-cache.ts +2 -0
  177. package/src/server/management-api.ts +23 -0
  178. package/src/server/ports.ts +17 -0
  179. package/src/server/relay-eager.ts +4 -1
  180. package/src/server/relay.ts +70 -10
  181. package/src/server/request-decompress.ts +6 -3
  182. package/src/server/responses/agent-task-recovery.ts +25 -32
  183. package/src/server/responses/codex-auth-error.ts +11 -0
  184. package/src/server/responses/codex-ws-exchange.ts +52 -3
  185. package/src/server/responses/codex-ws-wire.ts +55 -0
  186. package/src/server/responses/compact.ts +9 -1
  187. package/src/server/responses/core.ts +337 -73
  188. package/src/server/responses/encrypted-payload.ts +45 -2
  189. package/src/server/responses/ws-upstream.ts +4 -1
  190. package/src/server/responses-self-named-namespace-scrub.ts +1 -3
  191. package/src/server/responses-undeclared-tool-guard.ts +1 -1
  192. package/src/server/search.ts +3 -0
  193. package/src/server/sse-payload-rewrite.ts +136 -51
  194. package/src/server/ws-bridge.ts +35 -1
  195. package/src/service/cli.ts +372 -0
  196. package/src/service/diagnostics.ts +340 -0
  197. package/src/service/guards.ts +303 -0
  198. package/src/service/health.ts +222 -0
  199. package/src/service/launchd.ts +853 -0
  200. package/src/service/orchestration.ts +617 -0
  201. package/src/service/repair.ts +334 -0
  202. package/src/service/state.ts +363 -0
  203. package/src/service/systemd.ts +229 -0
  204. package/src/service/windows-ops.ts +690 -0
  205. package/src/service/windows-scheduler.ts +769 -0
  206. package/src/service/windows-taskxml.ts +613 -0
  207. package/src/service.ts +22 -5550
  208. package/src/storage/cleanup/db.ts +258 -0
  209. package/src/storage/cleanup/execute.ts +358 -0
  210. package/src/storage/cleanup/paths.ts +189 -0
  211. package/src/storage/cleanup/pending.ts +140 -0
  212. package/src/storage/cleanup/preview.ts +292 -0
  213. package/src/storage/cleanup/reconcile.ts +347 -0
  214. package/src/storage/cleanup/restore.ts +932 -0
  215. package/src/storage/cleanup/satellite.ts +474 -0
  216. package/src/storage/cleanup/staging.ts +129 -0
  217. package/src/storage/cleanup/types.ts +98 -0
  218. package/src/storage/cleanup.ts +49 -3127
  219. package/src/types/accounts.ts +2 -0
  220. package/src/types/config.ts +13 -12
  221. package/src/types/provider.ts +37 -0
  222. package/src/types/request.ts +2 -0
  223. package/src/types/tools.ts +17 -5
  224. package/src/types.ts +1 -0
  225. package/src/usage/expected-prices.ts +127 -0
  226. package/src/usage/log.ts +58 -1
  227. package/src/vision/eligibility.ts +13 -2
  228. package/src/web-search/loop.ts +56 -3
  229. package/gui/dist/assets/index-CWXut3rG.js +0 -115
  230. package/gui/dist/assets/index-EdoPnm9_.css +0 -1
  231. package/src/adapters/devin-cli/acp.ts +0 -204
  232. package/src/adapters/devin-cli/adapter.ts +0 -345
  233. package/src/adapters/devin-cli/binary.ts +0 -69
  234. package/src/adapters/devin-cli/models.ts +0 -57
  235. package/src/oauth/devin-cli.ts +0 -149
  236. package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
@@ -1,149 +0,0 @@
1
- /**
2
- * Devin CLI credential import.
3
- *
4
- * The installed CLI writes `credentials.toml` after `devin auth login`, and the
5
- * `windsurf_api_key` in it is an ordinary `devin-session-token$<JWT>` — the same
6
- * shape RegisterUser returns for `ocx login devin`, and the same one the
7
- * cloud-direct client already speaks. Measured against a signed-in CLI: it mints
8
- * a user_jwt, opens the full model catalog, and streams chat.
9
- *
10
- * So this is kiro's import-first login with the same substance: adopt a signed-in
11
- * local CLI's own session rather than starting a browser flow the CLI already
12
- * completed. No browser is ever opened, because there is nothing left for
13
- * opencodex to authorize.
14
- *
15
- * The file also carries `devin_webapp_host` and `devin_api_url`, which belong to
16
- * the Devin *session* product (`cog_` keys, agent VMs) rather than to model
17
- * inference. Neither is read here.
18
- */
19
- import { existsSync, readFileSync } from "node:fs";
20
- import { homedir } from "node:os";
21
- import { posix, win32 } from "node:path";
22
- import { DEVIN_CLI_INSTALL_HINT } from "../adapters/devin-cli/binary";
23
- import { identityFromApiKey } from "./devin";
24
- import { resolveDevinApiBaseUrl } from "./devin/api-base";
25
- import type { OAuthController, OAuthCredentials } from "./types";
26
-
27
- /**
28
- * Structurally the `LoginOpts` from `./index`, restated here rather than imported.
29
- * `index.ts` imports this module to register the provider, so importing the type
30
- * back would close a cycle for one optional field this flow does not branch on:
31
- * an import has nothing to force, so `forceLogin` is a no-op for it.
32
- */
33
- type DevinCliLoginOpts = { forceLogin?: boolean };
34
-
35
- /** Absolute-path override, for a CLI installed somewhere this resolver does not model. */
36
- export const DEVIN_CLI_CREDENTIALS_ENV = "OPENCODEX_DEVIN_CLI_CREDENTIALS";
37
-
38
- export interface DevinCliLoginDeps {
39
- env?: NodeJS.ProcessEnv;
40
- platform?: NodeJS.Platform;
41
- exists?: (path: string) => boolean;
42
- read?: (path: string) => string;
43
- }
44
-
45
- /**
46
- * Where the CLI keeps its own credential.
47
- *
48
- * Measured on a live install: `$XDG_DATA_HOME/devin/credentials.toml`, i.e.
49
- * `~/.local/share/devin/...`, which `devin auth status` prints. Note this is the
50
- * DATA dir, not the config dir — an earlier draft guessed `~/.config` and was
51
- * wrong. The Windows branch mirrors the CLI's own installer.
52
- */
53
- export function devinCliCredentialsPath(
54
- env: NodeJS.ProcessEnv = process.env,
55
- platform: NodeJS.Platform = process.platform,
56
- ): string {
57
- const override = env[DEVIN_CLI_CREDENTIALS_ENV]?.trim();
58
- // Absolute only. A relative override would resolve against whatever directory
59
- // the proxy happens to be running in, which is not a location a user can mean.
60
- if (override && (override.startsWith("/") || /^[A-Za-z]:[\\/]/.test(override))) return override;
61
- const paths = platform === "win32" ? win32 : posix;
62
- if (platform === "win32") {
63
- const appData = env.APPDATA ?? paths.join(homedir(), "AppData", "Roaming");
64
- return paths.join(appData, "devin", "credentials.toml");
65
- }
66
- const dataHome = env.XDG_DATA_HOME ?? paths.join(homedir(), ".local", "share");
67
- return paths.join(dataHome, "devin", "credentials.toml");
68
- }
69
-
70
- export interface DevinCliCredentialFile {
71
- apiKey: string;
72
- apiServerUrl: string;
73
- }
74
-
75
- /**
76
- * Read the two keys that matter, and nothing else.
77
- *
78
- * The measured file is four flat `key = "value"` lines: no tables, no comments,
79
- * no single quotes. A line matcher is therefore enough and a TOML dependency is
80
- * not, and the quoted form is required rather than optional — an unquoted
81
- * matcher would pass its own fixtures and miss the real file.
82
- *
83
- * Returns undefined rather than throwing so the caller owns the one error
84
- * message. Nothing here ever puts the file's contents into a thrown value.
85
- */
86
- export function readDevinCliCredentialFile(deps: DevinCliLoginDeps = {}): DevinCliCredentialFile | undefined {
87
- const path = devinCliCredentialsPath(deps.env, deps.platform);
88
- const exists = deps.exists ?? existsSync;
89
- if (!exists(path)) return undefined;
90
- let raw: string;
91
- try {
92
- raw = (deps.read ?? ((p: string) => readFileSync(p, "utf8")))(path);
93
- } catch {
94
- return undefined;
95
- }
96
- const apiKey = raw.match(/^\s*windsurf_api_key\s*=\s*"([^"]+)"/m)?.[1]?.trim();
97
- const apiServerUrl = raw.match(/^\s*api_server_url\s*=\s*"([^"]+)"/m)?.[1]?.trim();
98
- if (!apiKey || !apiServerUrl) return undefined;
99
- return { apiKey, apiServerUrl };
100
- }
101
-
102
- /** True when a signed-in CLI credential is readable. Used for status, never for auth. */
103
- export function devinCliSignedIn(deps: DevinCliLoginDeps = {}): boolean {
104
- return readDevinCliCredentialFile(deps) !== undefined;
105
- }
106
-
107
- export async function loginDevinCli(
108
- ctrl: OAuthController,
109
- _opts?: DevinCliLoginOpts,
110
- deps: DevinCliLoginDeps = {},
111
- ): Promise<OAuthCredentials> {
112
- const file = readDevinCliCredentialFile(deps);
113
- if (!file) {
114
- // Deliberately names no path contents and no parsed value. A Connect error
115
- // can echo a request, and redactSecretString does not recognise a bare JWT
116
- // or a devin-session-token, which is why register-user.ts refuses to repeat
117
- // error bodies; the same caution applies to anything thrown from here.
118
- throw new Error(
119
- `No signed-in Devin CLI session found. ${DEVIN_CLI_INSTALL_HINT} Then run \`devin auth login\` and try again.`,
120
- );
121
- }
122
- // The host comes off disk and then receives the key, so it passes the same
123
- // allowlist as the RegisterUser host. An unallowlisted value falls back to the
124
- // default rather than becoming an exfiltration target.
125
- const apiBaseUrl = resolveDevinApiBaseUrl(file.apiServerUrl);
126
- ctrl.onProgress?.("Imported the signed-in Devin CLI session.");
127
- return {
128
- access: file.apiKey,
129
- // Cognition issues a durable key and exposes no refresh endpoint. Carrying
130
- // the key here rather than "" is the house pattern: an empty refresh makes
131
- // detectOAuthWarning report stale_credentials from the moment of login.
132
- refresh: file.apiKey,
133
- expires: Number.MAX_SAFE_INTEGER,
134
- source: "local-cli",
135
- apiBaseUrl,
136
- ...identityFromApiKey(file.apiKey),
137
- };
138
- }
139
-
140
- export async function refreshDevinCliToken(
141
- _refreshToken: string,
142
- _signal?: AbortSignal,
143
- _credential?: OAuthCredentials,
144
- ): Promise<OAuthCredentials> {
145
- // The CLI owns this session and Cognition has no refresh endpoint. Extending
146
- // the stored expiry would make a revoked key look valid forever; throwing lets
147
- // the request path mark the account needsReauth instead.
148
- throw new Error("invalid_grant: the Devin CLI owns this session. Run devin auth login again.");
149
- }
@@ -1,178 +0,0 @@
1
- import type { SsePayloadRewrite } from "./sse-payload-rewrite";
2
-
3
- /**
4
- * Route content-channel reasoning from native-Responses upstreams through the
5
- * expandable summary channel (issue #45).
6
- *
7
- * Codex renders the expandable reasoning trace from the Responses reasoning
8
- * item's `summary[]` channel. DeepSeek's native `/responses` endpoint emits
9
- * raw thinking on the content channel instead (`response.reasoning_text.delta`
10
- * plus items with `content: [{type: "reasoning_text", text}]` and an empty
11
- * `summary`), so routed DeepSeek turns showed the "Worked for Xs" timer with
12
- * nothing to expand. Native OpenAI upstreams already emit summary-channel
13
- * events; this rewrite is a no-op for them (no reasoning_text events to
14
- * rewrite) and only engages when the upstream produces content-channel
15
- * reasoning.
16
- *
17
- * Replay compatibility: Codex echoes the reasoning item it received back into
18
- * the next request's input. DeepSeek's Responses API accepts summary-shaped
19
- * reasoning input items (verified live), so the rewrite round-trips.
20
- */
21
-
22
- function isPlainObject(value: unknown): value is Record<string, unknown> {
23
- return !!value && typeof value === "object" && !Array.isArray(value);
24
- }
25
-
26
- function reasoningTextOf(item: Record<string, unknown>): string {
27
- if (!Array.isArray(item.content)) return "";
28
- return item.content
29
- .filter((part): part is Record<string, unknown> => isPlainObject(part) && part.type === "reasoning_text")
30
- .map(part => (typeof part.text === "string" ? part.text : ""))
31
- .join("");
32
- }
33
-
34
- /** Move a reasoning item's content channel into the summary channel. */
35
- function reasoningItemToSummaryShape(item: Record<string, unknown>): Record<string, unknown> {
36
- if (item.type !== "reasoning") return item;
37
- // `encrypted_content` is opaque, state-bearing provider data, so the entire item must retain its
38
- // upstream shape unless that backend has an explicit replay contract permitting a rewrite. This
39
- // defensively protects content-channel backends that do issue blobs when the client replays the
40
- // stored item. The delta rewrite can still provide the expandable trace for the live turn.
41
- // DeepSeek — the provider this rewrite was verified against — is `statelessResponses` and issues
42
- // no blob, so it is unaffected.
43
- if (typeof item.encrypted_content === "string" && item.encrypted_content.length > 0) return item;
44
- const text = reasoningTextOf(item);
45
- // Items that already use the summary channel (or carry no content text at
46
- // all) are left untouched: rewriting them could clear a valid summary.
47
- if (text.length === 0) return item;
48
- const next: Record<string, unknown> = { ...item };
49
- delete next.content;
50
- next.summary = [{ type: "summary_text", text }];
51
- return next;
52
- }
53
-
54
- /**
55
- * Rewrite one parsed SSE payload in place of the content channel, or return
56
- * `null` when nothing changed (caller keeps the original payload).
57
- */
58
- function rewritePayload(payload: Record<string, unknown>): Record<string, unknown> | null {
59
- switch (payload.type) {
60
- case "response.reasoning_text.delta": {
61
- const next: Record<string, unknown> = {
62
- type: "response.reasoning_summary_text.delta",
63
- item_id: payload.item_id,
64
- output_index: payload.output_index,
65
- summary_index: 0,
66
- delta: payload.delta,
67
- };
68
- if (payload.sequence_number !== undefined) next.sequence_number = payload.sequence_number;
69
- return next;
70
- }
71
- case "response.reasoning_text.done": {
72
- const next: Record<string, unknown> = {
73
- type: "response.reasoning_summary_text.done",
74
- item_id: payload.item_id,
75
- output_index: payload.output_index,
76
- summary_index: 0,
77
- text: payload.text,
78
- };
79
- if (payload.sequence_number !== undefined) next.sequence_number = payload.sequence_number;
80
- return next;
81
- }
82
- default: {
83
- let changed = false;
84
- const next: Record<string, unknown> = { ...payload };
85
- if (isPlainObject(next.item) && next.item.type === "reasoning") {
86
- const rewritten = reasoningItemToSummaryShape(next.item);
87
- if (rewritten !== next.item) {
88
- next.item = rewritten;
89
- changed = true;
90
- }
91
- }
92
- // SSE event shape: {type: "response.completed", response: {output}}.
93
- const response = isPlainObject(next.response) ? { ...next.response } : null;
94
- if (response && Array.isArray(response.output)) {
95
- const output = response.output.map(item => {
96
- if (!isPlainObject(item) || item.type !== "reasoning") return item;
97
- const rewritten = reasoningItemToSummaryShape(item);
98
- if (rewritten !== item) changed = true;
99
- return rewritten;
100
- });
101
- if (changed) {
102
- response.output = output;
103
- next.response = response;
104
- }
105
- }
106
- // Bare response document shape (non-streaming passthrough):
107
- // {object: "response", output: [...]}.
108
- if (Array.isArray(next.output)) {
109
- const output = next.output.map(item => {
110
- if (!isPlainObject(item) || item.type !== "reasoning") return item;
111
- const rewritten = reasoningItemToSummaryShape(item);
112
- if (rewritten !== item) changed = true;
113
- return rewritten;
114
- });
115
- if (changed) next.output = output;
116
- }
117
- return changed ? next : null;
118
- }
119
- }
120
- }
121
-
122
- /** Payload rewrite for passthrough relays whose upstream emits content-channel reasoning. */
123
- export function createReasoningSummaryChannelPayloadRewrite(): SsePayloadRewrite {
124
- return (payload: string): string => {
125
- let parsed: unknown;
126
- try {
127
- parsed = JSON.parse(payload);
128
- } catch {
129
- return payload;
130
- }
131
- if (!isPlainObject(parsed)) return payload;
132
- const rewritten = rewritePayload(parsed);
133
- return rewritten !== null ? JSON.stringify(rewritten) : payload;
134
- };
135
- }
136
-
137
- /**
138
- * Object-level variant for the non-streaming passthrough: the bounded-JSON
139
- * relay bypasses the SSE payload rewrite, so reasoning items inside a full
140
- * Responses JSON document need the same normalization before plain JSON
141
- * serialization or forced JSON-to-SSE reframing. Returns the same reference
142
- * when nothing changed.
143
- */
144
- export function rewriteReasoningSummaryInJson(value: unknown): unknown {
145
- if (!isPlainObject(value)) return value;
146
- const rewritten = rewritePayload(value);
147
- return rewritten !== null ? rewritten : value;
148
- }
149
-
150
- /** String-level variant of {@link rewriteReasoningSummaryInJson}. */
151
- export function rewriteReasoningSummaryInJsonString(json: string): string {
152
- let parsed: unknown;
153
- try {
154
- parsed = JSON.parse(json);
155
- } catch {
156
- return json;
157
- }
158
- const rewritten = rewriteReasoningSummaryInJson(parsed);
159
- return rewritten === parsed ? json : JSON.stringify(rewritten);
160
- }
161
-
162
- /**
163
- * True when a routed native-Responses provider emits content-channel reasoning
164
- * (raw `reasoning_text`) instead of the summary channel. DeepSeek's
165
- * `/responses` endpoint is the current example: it ships raw thinking with an
166
- * empty `summary` and keeps `preserveReasoningContentModels` so multi-turn
167
- * replays round-trip.
168
- */
169
- export function routeUsesContentChannelReasoning(
170
- provider: { statelessResponses?: boolean; preserveReasoningContentModels?: string[] },
171
- modelId: string,
172
- ): boolean {
173
- if (provider.statelessResponses === true) return true;
174
- const preserved = provider.preserveReasoningContentModels;
175
- const normalizedModelId = modelId.toLowerCase();
176
- return Array.isArray(preserved)
177
- && preserved.some(id => id.toLowerCase() === normalizedModelId);
178
- }