@bitkyc08/opencodex 2.52.0 → 2.53.0-preview.20260913
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.
- package/gui/dist/assets/index-BBOZWGB6.css +1 -0
- package/gui/dist/assets/index-D7ynYo2K.js +128 -0
- package/gui/dist/index.html +2 -2
- package/native/remote-workspace-helper/Cargo.lock +130 -0
- package/native/remote-workspace-helper/Cargo.toml +24 -0
- package/native/remote-workspace-helper/src/main.rs +49 -0
- package/native/remote-workspace-helper/src/protocol.rs +246 -0
- package/native/remote-workspace-helper/src/sandbox/macos.rs +19 -0
- package/native/remote-workspace-helper/src/sandbox/mod.rs +77 -0
- package/native/remote-workspace-helper/src/sandbox/windows.rs +15 -0
- package/package.json +6 -1
- package/src/adapters/anthropic-image-normalize.ts +30 -2
- package/src/adapters/anthropic.ts +1 -1
- package/src/adapters/base.ts +8 -2
- package/src/adapters/cursor/cursor-errors.ts +12 -0
- package/src/adapters/cursor/thread-continuity.ts +93 -0
- package/src/adapters/cursor.ts +104 -73
- package/src/adapters/devin/cloud-direct/chat.ts +312 -23
- package/src/adapters/devin/cloud-direct/metadata.ts +31 -2
- package/src/adapters/devin/live-models.ts +70 -3
- package/src/adapters/devin.ts +281 -21
- package/src/adapters/google-wire-compiler.ts +14 -6
- package/src/adapters/google.ts +22 -8
- package/src/adapters/kiro/adapter.ts +316 -0
- package/src/adapters/kiro/conversation.ts +136 -0
- package/src/adapters/kiro/payload.ts +432 -0
- package/src/adapters/kiro/reasoning.ts +56 -0
- package/src/adapters/kiro/stream.ts +1153 -0
- package/src/adapters/kiro/usage.ts +223 -0
- package/src/adapters/kiro/wire.ts +76 -0
- package/src/adapters/kiro.ts +8 -2319
- package/src/adapters/mimo-free.ts +1 -1
- package/src/adapters/openai-chat-images.ts +101 -0
- package/src/adapters/openai-chat.ts +201 -181
- package/src/adapters/openai-responses.ts +92 -224
- package/src/adapters/registry.ts +0 -7
- package/src/adapters/run-turn-queue.ts +13 -6
- package/src/bridge.ts +14 -15
- package/src/chat/inbound.ts +29 -4
- package/src/chat/outbound.ts +145 -107
- package/src/claude/desktop-profile.ts +4 -6
- package/src/cli/account-api.ts +14 -0
- package/src/cli/account-extended.ts +1 -1
- package/src/cli/account-history.ts +60 -0
- package/src/cli/account-main.ts +80 -0
- package/src/cli/account.ts +11 -3
- package/src/cli/capabilities.ts +113 -0
- package/src/cli/catalog.ts +109 -0
- package/src/cli/dispatch.ts +9 -0
- package/src/cli/help.ts +2 -0
- package/src/cli/index.ts +2 -2
- package/src/cli/observe.ts +28 -1
- package/src/cli/opencode.ts +42 -8
- package/src/cli/provider-runtime.ts +11 -1
- package/src/cli/provider.ts +22 -2
- package/src/cli/registry.ts +21 -0
- package/src/cli/remote-workspace.ts +154 -0
- package/src/cli/status.ts +39 -7
- package/src/cli/usage-report.ts +14 -2
- package/src/client/hub-client.ts +34 -0
- package/src/client/hub-state.ts +9 -1
- package/src/codex/account-store.ts +78 -0
- package/src/codex/auth-api.ts +81 -54
- package/src/codex/auth-context.ts +45 -16
- package/src/codex/catalog/effort.ts +1 -1
- package/src/codex/catalog/metadata.ts +3 -6
- package/src/codex/catalog/native-models.ts +4 -4
- package/src/codex/catalog/parsing.ts +2 -20
- package/src/codex/catalog/provider-fetch.ts +10 -1
- package/src/codex/catalog/remote.ts +233 -0
- package/src/codex/catalog/sync.ts +403 -35
- package/src/codex/convergence.ts +1 -1
- package/src/codex/history-manifest.ts +36 -0
- package/src/codex/history-provider.ts +32 -5
- package/src/codex/inject.ts +9 -0
- package/src/codex/main-account.ts +113 -0
- package/src/codex/main-device-reauth-api.ts +89 -0
- package/src/codex/main-device-reauth.ts +217 -0
- package/src/codex/native-residue.ts +9 -2
- package/src/codex/quota-auto-refresh.ts +3 -2
- package/src/codex/quota-capacity.ts +98 -0
- package/src/codex/quota-history.ts +160 -0
- package/src/codex/quota-types.ts +8 -0
- package/src/codex/quota.ts +118 -91
- package/src/codex/refresh.ts +2 -1
- package/src/codex/routing.ts +90 -17
- package/src/codex/sync.ts +33 -4
- package/src/combos/request.ts +19 -1
- package/src/config/multi-agent-surface.ts +61 -0
- package/src/config/provider-validation.ts +176 -0
- package/src/config.ts +213 -11
- package/src/generated/compatibility-version.json +436 -168
- package/src/images/loop.ts +119 -36
- package/src/lib/admission.ts +12 -6
- package/src/lib/redact.ts +7 -0
- package/src/lib/translator-budget.ts +4 -3
- package/src/lib/windows-atomic-replace.ts +1 -0
- package/src/lib/windows-elevation.ts +1 -1
- package/src/oauth/chatgpt-device.ts +62 -5
- package/src/oauth/devin/cli-import.ts +130 -0
- package/src/oauth/devin.ts +63 -8
- package/src/oauth/index.ts +29 -14
- package/src/oauth/kiro.ts +18 -6
- package/src/oauth/login-cli.ts +9 -1
- package/src/oauth/meta-muse-device.ts +464 -0
- package/src/oauth/meta-muse.ts +123 -32
- package/src/oauth/pool-kernel.ts +9 -0
- package/src/oauth/pool-settings-capability.ts +2 -2
- package/src/oauth/store.ts +57 -0
- package/src/oauth/types.ts +31 -0
- package/src/providers/derive.ts +13 -3
- package/src/providers/devin-cli-authmode-migration.ts +57 -35
- package/src/providers/devin-provider-merge-migration.ts +240 -0
- package/src/providers/muse-key-quota.ts +117 -0
- package/src/providers/muse-subscription-usage.ts +14 -2
- package/src/providers/openai-sidecar.ts +25 -3
- package/src/providers/opencode-zen-rate-limit.ts +58 -0
- package/src/providers/provider-id-rewrite.ts +20 -5
- package/src/providers/quota-types.ts +12 -0
- package/src/providers/quota.ts +143 -102
- package/src/providers/reasoning-metadata.ts +543 -0
- package/src/providers/registry.ts +80 -49
- package/src/reasoning-effort.ts +26 -2
- package/src/remote/hub-usage.ts +32 -0
- package/src/remote-control/index.ts +192 -41
- package/src/remote-control/workspace-activation.ts +9 -0
- package/src/remote-control/workspace-agent-connection.ts +366 -0
- package/src/remote-control/workspace-claude-runtime.ts +243 -0
- package/src/remote-control/workspace-codex-runtime.ts +531 -0
- package/src/remote-control/workspace-codex-sandbox.ts +115 -0
- package/src/remote-control/workspace-command-runner.ts +748 -0
- package/src/remote-control/workspace-coordinator.ts +231 -0
- package/src/remote-control/workspace-device.ts +585 -0
- package/src/remote-control/workspace-executable.ts +43 -0
- package/src/remote-control/workspace-executor.ts +397 -0
- package/src/remote-control/workspace-hub.ts +519 -0
- package/src/remote-control/workspace-pi-runtime.ts +382 -0
- package/src/remote-control/workspace-process.ts +129 -0
- package/src/remote-control/workspace-rpc.ts +304 -0
- package/src/remote-control/workspace-runtime.ts +60 -0
- package/src/remote-control/workspace-secret-store.ts +39 -0
- package/src/remote-control/workspace-sessions.ts +799 -0
- package/src/remote-control/workspace-tool-bridge.ts +192 -0
- package/src/responses/code-mode-helper-compat.ts +22 -3
- package/src/responses/hosted-tool-policy.ts +0 -1
- package/src/responses/muse-tool-name-alias.ts +379 -0
- package/src/responses/plaintext-v2-agent-messages.ts +902 -0
- package/src/router.ts +7 -0
- package/src/routing/compatibility/behavior.ts +0 -1
- package/src/server/audio-client.ts +64 -0
- package/src/server/audio-dictation.ts +91 -0
- package/src/server/audio-live.ts +185 -0
- package/src/server/audio-transcriptions.ts +183 -0
- package/src/server/audio-upstream.ts +153 -0
- package/src/server/auth-cors.ts +61 -2
- package/src/server/chat-completions.ts +1 -1
- package/src/server/chat-native-sse.ts +92 -48
- package/src/server/chat-native.ts +37 -15
- package/src/server/hub-usage.ts +57 -0
- package/src/server/images.ts +4 -0
- package/src/server/index.ts +722 -57
- package/src/server/lifecycle.ts +5 -6
- package/src/server/live-call-bindings.ts +60 -0
- package/src/server/live.ts +12 -1
- package/src/server/management/agent-settings-routes.ts +25 -4
- package/src/server/management/api-access.ts +37 -0
- package/src/server/management/api-key-usage.ts +7 -2
- package/src/server/management/config-routes.ts +1 -18
- package/src/server/management/context.ts +15 -0
- package/src/server/management/logs-usage-routes.ts +2 -0
- package/src/server/management/oauth-account-routes.ts +39 -12
- package/src/server/management/provider-routes.ts +125 -2
- package/src/server/management/remote-workspace-routes.ts +140 -0
- package/src/server/management/route-registry.ts +15 -0
- package/src/server/management/usage-aggregate-cache.ts +14 -15
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management-api.ts +23 -0
- package/src/server/ports.ts +17 -0
- package/src/server/relay-eager.ts +4 -1
- package/src/server/relay.ts +70 -10
- package/src/server/request-decompress.ts +6 -3
- package/src/server/responses/agent-task-recovery.ts +25 -32
- package/src/server/responses/codex-auth-error.ts +11 -0
- package/src/server/responses/codex-ws-exchange.ts +52 -3
- package/src/server/responses/codex-ws-wire.ts +55 -0
- package/src/server/responses/compact.ts +9 -1
- package/src/server/responses/core.ts +337 -73
- package/src/server/responses/encrypted-payload.ts +45 -2
- package/src/server/responses/ws-upstream.ts +4 -1
- package/src/server/responses-self-named-namespace-scrub.ts +1 -3
- package/src/server/responses-undeclared-tool-guard.ts +1 -1
- package/src/server/search.ts +3 -0
- package/src/server/sse-payload-rewrite.ts +136 -51
- package/src/server/ws-bridge.ts +35 -1
- package/src/service/cli.ts +372 -0
- package/src/service/diagnostics.ts +340 -0
- package/src/service/guards.ts +303 -0
- package/src/service/health.ts +222 -0
- package/src/service/launchd.ts +853 -0
- package/src/service/orchestration.ts +617 -0
- package/src/service/repair.ts +334 -0
- package/src/service/state.ts +363 -0
- package/src/service/systemd.ts +229 -0
- package/src/service/windows-ops.ts +690 -0
- package/src/service/windows-scheduler.ts +769 -0
- package/src/service/windows-taskxml.ts +613 -0
- package/src/service.ts +22 -5550
- package/src/storage/cleanup/db.ts +258 -0
- package/src/storage/cleanup/execute.ts +358 -0
- package/src/storage/cleanup/paths.ts +189 -0
- package/src/storage/cleanup/pending.ts +140 -0
- package/src/storage/cleanup/preview.ts +292 -0
- package/src/storage/cleanup/reconcile.ts +347 -0
- package/src/storage/cleanup/restore.ts +932 -0
- package/src/storage/cleanup/satellite.ts +474 -0
- package/src/storage/cleanup/staging.ts +129 -0
- package/src/storage/cleanup/types.ts +98 -0
- package/src/storage/cleanup.ts +49 -3127
- package/src/types/accounts.ts +2 -0
- package/src/types/config.ts +13 -12
- package/src/types/provider.ts +37 -0
- package/src/types/request.ts +2 -0
- package/src/types/tools.ts +17 -5
- package/src/types.ts +1 -0
- package/src/usage/expected-prices.ts +127 -0
- package/src/usage/log.ts +58 -1
- package/src/vision/eligibility.ts +13 -2
- package/src/web-search/loop.ts +56 -3
- package/gui/dist/assets/index-CWXut3rG.js +0 -115
- package/gui/dist/assets/index-EdoPnm9_.css +0 -1
- package/src/adapters/devin-cli/acp.ts +0 -204
- package/src/adapters/devin-cli/adapter.ts +0 -345
- package/src/adapters/devin-cli/binary.ts +0 -69
- package/src/adapters/devin-cli/models.ts +0 -57
- package/src/oauth/devin-cli.ts +0 -149
- package/src/server/responses-reasoning-summary-rewrite.ts +0 -178
package/src/oauth/devin-cli.ts
DELETED
|
@@ -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
|
-
}
|