@ycodium-ai/agent-codex 0.2.2098 → 0.2.2126
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/dist/index.js +1028 -362
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -43,6 +43,8 @@ var CODEX_CARD = {
|
|
|
43
43
|
sessionModelSwitch: "in-session",
|
|
44
44
|
exclusiveSessionWriter: true,
|
|
45
45
|
runtimeModeSwitch: "next-turn",
|
|
46
|
+
// 三字段组合在 openThread 时定,切档要重开会话(与 runtimeModeSwitch 同为 next-turn)。
|
|
47
|
+
nativeModeSwitch: true,
|
|
46
48
|
contextCompaction: "native"
|
|
47
49
|
},
|
|
48
50
|
credentialSources: [],
|
|
@@ -69,7 +71,22 @@ var CODEX_CARD = {
|
|
|
69
71
|
acceptsExternalEndpoints: true,
|
|
70
72
|
nativePathSuffix: "/responses",
|
|
71
73
|
credentialEnvKey: "CODEX_API_KEY",
|
|
72
|
-
supportsNativeSubscription: true
|
|
74
|
+
supportsNativeSubscription: true,
|
|
75
|
+
// codex 不认「地址一个变量」:端点写在 `-c model_providers.<id>.base_url` 上,
|
|
76
|
+
// 凭据只能经环境变量、变量名还得告诉它。`$providerId` / `$credentialEnvKey`
|
|
77
|
+
// 由宿主按 binding 生成,同一台机器上两份绑定因此不抢同一个变量名。
|
|
78
|
+
//
|
|
79
|
+
// `forced_login_method=api` 要写在最后:chatgpt 实例自己先注了
|
|
80
|
+
// `forced_login_method=chatgpt`,不盖掉的话手仍走订阅登录,代理地址白注。
|
|
81
|
+
extraLaunchArgs: [
|
|
82
|
+
{ key: "model_provider", value: "$providerId" },
|
|
83
|
+
{ key: "model_providers.$providerId.name", value: "Ycodium API" },
|
|
84
|
+
{ key: "model_providers.$providerId.base_url", value: "$baseUrl" },
|
|
85
|
+
{ key: "model_providers.$providerId.env_key", value: "$credentialEnvKey" },
|
|
86
|
+
{ key: "model_providers.$providerId.requires_openai_auth", value: false },
|
|
87
|
+
{ key: "model_providers.$providerId.wire_api", value: "responses" },
|
|
88
|
+
{ key: "forced_login_method", value: "api" }
|
|
89
|
+
]
|
|
73
90
|
}
|
|
74
91
|
};
|
|
75
92
|
|
|
@@ -172,6 +189,7 @@ var readCodexAccount = (homePath) => {
|
|
|
172
189
|
};
|
|
173
190
|
|
|
174
191
|
// src/config.ts
|
|
192
|
+
import { modelSelectionStringOption } from "@ycodium-ai/plugin-api/executor";
|
|
175
193
|
var CODEX_INSTANCE_API_KEY_ENV = "YCODIUM_CODEX_API_KEY";
|
|
176
194
|
var CODEX_INSTANCE_MODEL_PROVIDER = "ycodium_api";
|
|
177
195
|
function isRecord2(value) {
|
|
@@ -299,6 +317,40 @@ function codexExecLaunchArgs(launchArgs) {
|
|
|
299
317
|
function hasConfiguredMcpServer(appServerArgs) {
|
|
300
318
|
return appServerArgs.some((arg) => arg.includes("mcp_servers."));
|
|
301
319
|
}
|
|
320
|
+
var BUILTIN_MCP_SERVER_NAME = "ycodium";
|
|
321
|
+
function codexBearerTokenEnvVar(name) {
|
|
322
|
+
return name === BUILTIN_MCP_SERVER_NAME ? "YCODIUM_MCP_BEARER_TOKEN" : `YCODIUM_MCP_TOKEN_${name.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}`;
|
|
323
|
+
}
|
|
324
|
+
function codexMcpLaunchConfig(servers) {
|
|
325
|
+
const args = [];
|
|
326
|
+
const environment = {};
|
|
327
|
+
const toml = (value) => JSON.stringify(value);
|
|
328
|
+
for (const server of servers) {
|
|
329
|
+
const key = `mcp_servers.${server.name}`;
|
|
330
|
+
if (server.transport.type === "stdio") {
|
|
331
|
+
args.push("-c", `${key}.command=${toml(server.transport.command)}`);
|
|
332
|
+
const commandArgs = server.transport.args ?? [];
|
|
333
|
+
if (commandArgs.length > 0) {
|
|
334
|
+
args.push("-c", `${key}.args=[${commandArgs.map(toml).join(",")}]`);
|
|
335
|
+
}
|
|
336
|
+
for (const [name, value] of Object.entries(server.transport.env ?? {})) {
|
|
337
|
+
args.push("-c", `${key}.env.${name}=${toml(value)}`);
|
|
338
|
+
}
|
|
339
|
+
continue;
|
|
340
|
+
}
|
|
341
|
+
args.push("-c", `${key}.url=${server.transport.url}`);
|
|
342
|
+
const bearer = server.transport.headers?.["Authorization"]?.replace(/^Bearer\s+/, "");
|
|
343
|
+
if (bearer) {
|
|
344
|
+
const envVar = codexBearerTokenEnvVar(server.name);
|
|
345
|
+
environment[envVar] = bearer;
|
|
346
|
+
args.push("-c", `${key}.bearer_token_env_var=${toml(envVar)}`);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return { args, environment };
|
|
350
|
+
}
|
|
351
|
+
function codexOverrideArgs(overrides) {
|
|
352
|
+
return overrides.flatMap(({ key, value }) => ["-c", `${key}=${JSON.stringify(value)}`]);
|
|
353
|
+
}
|
|
302
354
|
function encodeCodexConfigOverrides(overrides) {
|
|
303
355
|
return overrides.map(({ key, value }) => `-c ${JSON.stringify(`${key}=${JSON.stringify(value)}`)}`).join(" ");
|
|
304
356
|
}
|
|
@@ -309,44 +361,9 @@ function modelIdFromSelection(selection) {
|
|
|
309
361
|
const model = selection["model"];
|
|
310
362
|
return typeof model === "string" ? model.trim() || void 0 : void 0;
|
|
311
363
|
}
|
|
312
|
-
function modelSelectionStringOption(selection, optionId) {
|
|
313
|
-
if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
|
|
314
|
-
return void 0;
|
|
315
|
-
}
|
|
316
|
-
const options = selection["options"];
|
|
317
|
-
if (!Array.isArray(options)) return void 0;
|
|
318
|
-
for (const entry of options) {
|
|
319
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) continue;
|
|
320
|
-
const id = entry["id"];
|
|
321
|
-
if (id !== optionId) continue;
|
|
322
|
-
const value = entry["value"];
|
|
323
|
-
return typeof value === "string" ? value.trim() || void 0 : void 0;
|
|
324
|
-
}
|
|
325
|
-
return void 0;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// src/paths.ts
|
|
329
|
-
import * as NodeOS from "node:os";
|
|
330
|
-
import * as NodePath2 from "node:path";
|
|
331
|
-
function expandHomePath(value) {
|
|
332
|
-
if (value === "~") return NodeOS.homedir();
|
|
333
|
-
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
334
|
-
return NodePath2.join(NodeOS.homedir(), value.slice(2));
|
|
335
|
-
}
|
|
336
|
-
return value;
|
|
337
|
-
}
|
|
338
|
-
function compactEnv(env) {
|
|
339
|
-
const out = {};
|
|
340
|
-
for (const [key, value] of Object.entries(env)) {
|
|
341
|
-
if (value !== void 0) out[key] = value;
|
|
342
|
-
}
|
|
343
|
-
return out;
|
|
344
|
-
}
|
|
345
|
-
function nowIso() {
|
|
346
|
-
return (/* @__PURE__ */ new Date()).toISOString();
|
|
347
|
-
}
|
|
348
364
|
|
|
349
365
|
// src/connection.ts
|
|
366
|
+
import { mergeLaunchEnv, brainSlotEnvKeys } from "@ycodium-ai/plugin-api/executor";
|
|
350
367
|
function codexApiConnectionError(recipe, environment) {
|
|
351
368
|
if (recipe.authMode !== "apiKey") return null;
|
|
352
369
|
try {
|
|
@@ -386,38 +403,40 @@ function connectionArgv(recipe, layout) {
|
|
|
386
403
|
}
|
|
387
404
|
return args;
|
|
388
405
|
}
|
|
389
|
-
function codexSpawnArgs(recipe, layout) {
|
|
406
|
+
function codexSpawnArgs(recipe, layout, nativeSubscription = false) {
|
|
407
|
+
if (nativeSubscription) {
|
|
408
|
+
return [
|
|
409
|
+
...tokenizeLaunchArgs(recipe.launchArgs),
|
|
410
|
+
"-c",
|
|
411
|
+
`model_provider=${JSON.stringify("openai")}`,
|
|
412
|
+
"-c",
|
|
413
|
+
`forced_login_method=${JSON.stringify("chatgpt")}`
|
|
414
|
+
];
|
|
415
|
+
}
|
|
390
416
|
const needsOverrides = layout.mode === "authOverlay" || recipe.authMode !== "default";
|
|
391
417
|
if (!needsOverrides) return tokenizeLaunchArgs(recipe.launchArgs);
|
|
392
418
|
return connectionArgv(recipe, layout);
|
|
393
419
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
return out;
|
|
399
|
-
}
|
|
420
|
+
var CRUSHED_API_KEYS = {
|
|
421
|
+
OPENAI_API_KEY: void 0,
|
|
422
|
+
CODEX_API_KEY: void 0
|
|
423
|
+
};
|
|
400
424
|
function buildCodexSpawnEnv(input) {
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
} else {
|
|
410
|
-
delete withoutHomeOverride["HOME"];
|
|
411
|
-
}
|
|
412
|
-
return input.crushApiKeys ? crushInheritedApiKeys(withoutHomeOverride) : withoutHomeOverride;
|
|
425
|
+
const { HOME: _instanceHome, ...instanceTable } = input.recipe.environment;
|
|
426
|
+
return mergeLaunchEnv({
|
|
427
|
+
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
428
|
+
accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
|
|
429
|
+
instanceTable,
|
|
430
|
+
handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
|
|
431
|
+
launch: input.launchEnv
|
|
432
|
+
});
|
|
413
433
|
}
|
|
414
434
|
function nativeLoginSpawnEnv(processEnv, isolatedHome) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
return merged;
|
|
435
|
+
return mergeLaunchEnv({
|
|
436
|
+
brainSlots: brainSlotEnvKeys(CODEX_CARD),
|
|
437
|
+
inherited: processEnv,
|
|
438
|
+
handOverrides: { ...CRUSHED_API_KEYS, CODEX_HOME: isolatedHome }
|
|
439
|
+
});
|
|
421
440
|
}
|
|
422
441
|
function shouldCrushApiKeys(authMode) {
|
|
423
442
|
return authMode === "chatgpt";
|
|
@@ -427,6 +446,22 @@ function shouldCrushApiKeys(authMode) {
|
|
|
427
446
|
import * as NodeFSP from "node:fs/promises";
|
|
428
447
|
import * as NodeOS2 from "node:os";
|
|
429
448
|
import * as NodePath3 from "node:path";
|
|
449
|
+
|
|
450
|
+
// src/paths.ts
|
|
451
|
+
import * as NodeOS from "node:os";
|
|
452
|
+
import * as NodePath2 from "node:path";
|
|
453
|
+
function expandHomePath(value) {
|
|
454
|
+
if (value === "~") return NodeOS.homedir();
|
|
455
|
+
if (value.startsWith("~/") || value.startsWith("~\\")) {
|
|
456
|
+
return NodePath2.join(NodeOS.homedir(), value.slice(2));
|
|
457
|
+
}
|
|
458
|
+
return value;
|
|
459
|
+
}
|
|
460
|
+
function nowIso() {
|
|
461
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// src/homeLayout.ts
|
|
430
465
|
var KNOWN_SHARED_DIRECTORIES = [
|
|
431
466
|
"sessions",
|
|
432
467
|
"archived_sessions",
|
|
@@ -512,6 +547,16 @@ async function ensureSymlink(input) {
|
|
|
512
547
|
await createEntryLink(target, link);
|
|
513
548
|
}
|
|
514
549
|
}
|
|
550
|
+
async function makeDirectory(directoryPath) {
|
|
551
|
+
try {
|
|
552
|
+
await NodeFSP.mkdir(directoryPath, { recursive: true });
|
|
553
|
+
} catch (error) {
|
|
554
|
+
throw new Error(
|
|
555
|
+
`Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
|
|
556
|
+
{ cause: error }
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
}
|
|
515
560
|
async function materializeCodexShadowHome(layout, log) {
|
|
516
561
|
if (layout.mode !== "authOverlay") return;
|
|
517
562
|
const effectiveHomePath = layout.effectiveHomePath;
|
|
@@ -521,11 +566,11 @@ async function materializeCodexShadowHome(layout, log) {
|
|
|
521
566
|
`Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
|
|
522
567
|
);
|
|
523
568
|
}
|
|
524
|
-
await
|
|
525
|
-
await
|
|
569
|
+
await makeDirectory(layout.sharedHomePath);
|
|
570
|
+
await makeDirectory(effectiveHomePath);
|
|
526
571
|
await Promise.all(
|
|
527
572
|
KNOWN_SHARED_DIRECTORIES.map(
|
|
528
|
-
(directory) =>
|
|
573
|
+
(directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
|
|
529
574
|
)
|
|
530
575
|
);
|
|
531
576
|
let sharedEntryNames = [];
|
|
@@ -584,6 +629,73 @@ var ACTIVE_WRITER_ATTEMPTS = 5;
|
|
|
584
629
|
var ACTIVE_WRITER_DELAY_MS = 200;
|
|
585
630
|
var DEFAULT_MODEL = "gpt-5.6-sol";
|
|
586
631
|
var DEFAULT_TEXT_GENERATION_REASONING_EFFORT = "low";
|
|
632
|
+
var CODEX_NATIVE_MODES = [
|
|
633
|
+
{
|
|
634
|
+
id: "read-only",
|
|
635
|
+
name: "Read only",
|
|
636
|
+
description: "Sandboxed to reading. Asks before anything that writes or runs."
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
id: "workspace-write",
|
|
640
|
+
name: "Workspace write",
|
|
641
|
+
description: "Writes inside the workspace. Asks you before stepping outside it."
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
id: "auto-review",
|
|
645
|
+
name: "Auto review",
|
|
646
|
+
description: "Writes inside the workspace. Codex approves its own requests, not you."
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
id: "full-access",
|
|
650
|
+
name: "Full access",
|
|
651
|
+
description: "No sandbox, never asks. Every command runs immediately."
|
|
652
|
+
}
|
|
653
|
+
];
|
|
654
|
+
var CODEX_DEFAULT_NATIVE_MODE_ID = "read-only";
|
|
655
|
+
var CODEX_NATIVE_MODE_BY_RUNTIME_MODE = {
|
|
656
|
+
"approval-required": "read-only",
|
|
657
|
+
"auto-accept-edits": "workspace-write",
|
|
658
|
+
auto: "auto-review",
|
|
659
|
+
"full-access": "full-access"
|
|
660
|
+
};
|
|
661
|
+
var CODEX_NATIVE_MODE_REPORT = {
|
|
662
|
+
nativeModes: CODEX_NATIVE_MODES.map((mode) => ({
|
|
663
|
+
id: mode.id,
|
|
664
|
+
name: mode.name,
|
|
665
|
+
description: mode.description
|
|
666
|
+
})),
|
|
667
|
+
currentNativeModeId: CODEX_DEFAULT_NATIVE_MODE_ID,
|
|
668
|
+
nativeModeByRuntimeMode: CODEX_NATIVE_MODE_BY_RUNTIME_MODE
|
|
669
|
+
};
|
|
670
|
+
function isCodexNativeModeId(value) {
|
|
671
|
+
return CODEX_NATIVE_MODES.some((mode) => mode.id === value);
|
|
672
|
+
}
|
|
673
|
+
function codexNativeModeFor(runtimeMode, nativeModeId) {
|
|
674
|
+
return nativeModeId !== void 0 && isCodexNativeModeId(nativeModeId) ? nativeModeId : CODEX_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
|
|
675
|
+
}
|
|
676
|
+
function codexThreadConfigForNativeMode(nativeModeId) {
|
|
677
|
+
switch (nativeModeId) {
|
|
678
|
+
case "workspace-write":
|
|
679
|
+
return runtimeModeToThreadConfig("auto-accept-edits");
|
|
680
|
+
case "auto-review":
|
|
681
|
+
return runtimeModeToThreadConfig("auto");
|
|
682
|
+
case "full-access":
|
|
683
|
+
return runtimeModeToThreadConfig("full-access");
|
|
684
|
+
default:
|
|
685
|
+
return runtimeModeToThreadConfig("approval-required");
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function codexTurnSandboxPolicyForNativeMode(nativeModeId) {
|
|
689
|
+
switch (nativeModeId) {
|
|
690
|
+
case "workspace-write":
|
|
691
|
+
case "auto-review":
|
|
692
|
+
return runtimeModeToTurnSandboxPolicy("auto-accept-edits");
|
|
693
|
+
case "full-access":
|
|
694
|
+
return runtimeModeToTurnSandboxPolicy("full-access");
|
|
695
|
+
default:
|
|
696
|
+
return runtimeModeToTurnSandboxPolicy("approval-required");
|
|
697
|
+
}
|
|
698
|
+
}
|
|
587
699
|
function runtimeModeToThreadConfig(mode) {
|
|
588
700
|
switch (mode) {
|
|
589
701
|
case "approval-required":
|
|
@@ -617,6 +729,16 @@ function runtimeModeToTurnSandboxPolicy(mode) {
|
|
|
617
729
|
return { type: "dangerFullAccess" };
|
|
618
730
|
}
|
|
619
731
|
}
|
|
732
|
+
function codexCollaborationMode(input) {
|
|
733
|
+
const effort = input.reasoningEffort?.trim();
|
|
734
|
+
return {
|
|
735
|
+
mode: input.interactionMode === "plan" ? "plan" : "default",
|
|
736
|
+
settings: {
|
|
737
|
+
model: input.model,
|
|
738
|
+
...effort ? { reasoning_effort: effort } : {}
|
|
739
|
+
}
|
|
740
|
+
};
|
|
741
|
+
}
|
|
620
742
|
function isRecord3(value) {
|
|
621
743
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
622
744
|
}
|
|
@@ -697,11 +819,19 @@ var CHILD_AGENT_EVENT_METHODS = /* @__PURE__ */ new Set([
|
|
|
697
819
|
]);
|
|
698
820
|
var CHILD_CHATTER_METHODS = /* @__PURE__ */ new Set([
|
|
699
821
|
"item/agentMessage/delta",
|
|
700
|
-
"item/reasoning/
|
|
701
|
-
"item/reasoning/
|
|
822
|
+
"item/reasoning/textDelta",
|
|
823
|
+
"item/reasoning/summaryTextDelta",
|
|
702
824
|
"item/reasoning/summaryPartAdded",
|
|
825
|
+
"item/commandExecution/outputDelta",
|
|
826
|
+
"item/fileChange/outputDelta",
|
|
827
|
+
"item/fileChange/patchUpdated",
|
|
703
828
|
"item/plan/delta",
|
|
829
|
+
"turn/plan/updated",
|
|
830
|
+
"turn/diff/updated",
|
|
704
831
|
"thread/name/updated",
|
|
832
|
+
"thread/settings/updated",
|
|
833
|
+
"rawResponseItem/completed",
|
|
834
|
+
// 子线程自己的生命周期通知不能改父线程状态(旧宿主 runtime 同名单)。
|
|
705
835
|
"thread/archived",
|
|
706
836
|
"thread/unarchived",
|
|
707
837
|
"thread/compacted",
|
|
@@ -1120,95 +1250,6 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1120
1250
|
<runtime_info>In case you're asked: you are running in Ycodium through the Codex harness, as ${toSingleLine(runtime.model)} with ${toSingleLine(runtime.reasoningEffort)} reasoning effort. No need to mention this otherwise.</runtime_info>`;
|
|
1121
1251
|
}
|
|
1122
1252
|
|
|
1123
|
-
// src/imageMime.ts
|
|
1124
|
-
var IMAGE_EXTENSION_BY_MIME_TYPE = {
|
|
1125
|
-
"image/avif": ".avif",
|
|
1126
|
-
"image/bmp": ".bmp",
|
|
1127
|
-
"image/gif": ".gif",
|
|
1128
|
-
"image/heic": ".heic",
|
|
1129
|
-
"image/heif": ".heif",
|
|
1130
|
-
"image/jpeg": ".jpg",
|
|
1131
|
-
"image/jpg": ".jpg",
|
|
1132
|
-
"image/png": ".png",
|
|
1133
|
-
"image/svg+xml": ".svg",
|
|
1134
|
-
"image/tiff": ".tiff",
|
|
1135
|
-
"image/webp": ".webp"
|
|
1136
|
-
};
|
|
1137
|
-
var SAFE_IMAGE_FILE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
1138
|
-
".avif",
|
|
1139
|
-
".bmp",
|
|
1140
|
-
".gif",
|
|
1141
|
-
".heic",
|
|
1142
|
-
".heif",
|
|
1143
|
-
".ico",
|
|
1144
|
-
".jpeg",
|
|
1145
|
-
".jpg",
|
|
1146
|
-
".png",
|
|
1147
|
-
".svg",
|
|
1148
|
-
".tiff",
|
|
1149
|
-
".webp"
|
|
1150
|
-
]);
|
|
1151
|
-
function isBase64Char(code) {
|
|
1152
|
-
return code >= 97 && code <= 122 || code >= 65 && code <= 90 || code >= 48 && code <= 57 || code === 43 || code === 47 || code === 61;
|
|
1153
|
-
}
|
|
1154
|
-
function isBase64Whitespace(code) {
|
|
1155
|
-
return code === 13 || code === 10 || code === 32;
|
|
1156
|
-
}
|
|
1157
|
-
function parseBase64DataUrl(dataUrl) {
|
|
1158
|
-
const trimmed = dataUrl.trim();
|
|
1159
|
-
if (trimmed.slice(0, 5).toLowerCase() !== "data:") return null;
|
|
1160
|
-
const commaIndex = trimmed.indexOf(",");
|
|
1161
|
-
if (commaIndex === -1) return null;
|
|
1162
|
-
const header = trimmed.slice(5, commaIndex);
|
|
1163
|
-
if (header.length === 0) return null;
|
|
1164
|
-
const headerParts = [];
|
|
1165
|
-
for (const part of header.split(";")) {
|
|
1166
|
-
const partTrimmed = part.trim();
|
|
1167
|
-
if (partTrimmed.length > 0) headerParts.push(partTrimmed);
|
|
1168
|
-
}
|
|
1169
|
-
if (headerParts.length < 2) return null;
|
|
1170
|
-
const trailingToken = headerParts.at(-1)?.toLowerCase();
|
|
1171
|
-
if (trailingToken !== "base64") return null;
|
|
1172
|
-
const mimeType = headerParts[0]?.toLowerCase();
|
|
1173
|
-
if (!mimeType) return null;
|
|
1174
|
-
const payload = trimmed.slice(commaIndex + 1);
|
|
1175
|
-
const runs = [];
|
|
1176
|
-
let runStart = -1;
|
|
1177
|
-
for (let index = 0; index < payload.length; index += 1) {
|
|
1178
|
-
const code = payload.charCodeAt(index);
|
|
1179
|
-
if (isBase64Char(code)) {
|
|
1180
|
-
if (runStart === -1) runStart = index;
|
|
1181
|
-
continue;
|
|
1182
|
-
}
|
|
1183
|
-
if (!isBase64Whitespace(code)) return null;
|
|
1184
|
-
if (runStart !== -1) {
|
|
1185
|
-
runs.push(payload.slice(runStart, index));
|
|
1186
|
-
runStart = -1;
|
|
1187
|
-
}
|
|
1188
|
-
}
|
|
1189
|
-
if (runStart !== -1) runs.push(payload.slice(runStart));
|
|
1190
|
-
const base64 = runs.length === 1 ? runs[0] : runs.join("");
|
|
1191
|
-
if (base64.length === 0 || base64.length % 4 !== 0) return null;
|
|
1192
|
-
const firstPad = base64.indexOf("=");
|
|
1193
|
-
if (firstPad !== -1) {
|
|
1194
|
-
if (base64.length - firstPad > 2) return null;
|
|
1195
|
-
for (let index = firstPad; index < base64.length; index += 1) {
|
|
1196
|
-
if (base64.charCodeAt(index) !== 61) return null;
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
return { mimeType, base64 };
|
|
1200
|
-
}
|
|
1201
|
-
function inferImageExtension(input) {
|
|
1202
|
-
const key = input.mimeType.toLowerCase();
|
|
1203
|
-
const fromMime = Object.hasOwn(IMAGE_EXTENSION_BY_MIME_TYPE, key) ? IMAGE_EXTENSION_BY_MIME_TYPE[key] : void 0;
|
|
1204
|
-
if (fromMime) return fromMime;
|
|
1205
|
-
const fileName = input.fileName?.trim() ?? "";
|
|
1206
|
-
const extensionMatch = /\.([a-z0-9]{1,8})$/i.exec(fileName);
|
|
1207
|
-
const fileNameExtension = extensionMatch ? `.${extensionMatch[1].toLowerCase()}` : "";
|
|
1208
|
-
if (SAFE_IMAGE_FILE_EXTENSIONS.has(fileNameExtension)) return fileNameExtension;
|
|
1209
|
-
return ".bin";
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
1253
|
// src/events.ts
|
|
1213
1254
|
var CODEX_PROVIDER = "codex";
|
|
1214
1255
|
var eventSeq = 0;
|
|
@@ -1225,28 +1266,27 @@ function nowIso2() {
|
|
|
1225
1266
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1226
1267
|
}
|
|
1227
1268
|
function normalizeItemType(value) {
|
|
1228
|
-
if (!value) return "
|
|
1229
|
-
return value.replace(/([a-
|
|
1269
|
+
if (!value) return "item";
|
|
1270
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
1230
1271
|
}
|
|
1231
1272
|
function toCanonicalItemType(raw) {
|
|
1232
1273
|
const type = normalizeItemType(raw);
|
|
1233
|
-
if (type.includes("user")
|
|
1234
|
-
if (type.includes("agent")
|
|
1235
|
-
if (type.includes("
|
|
1236
|
-
if (type.includes("reason") || type.includes("thought")) return "reasoning";
|
|
1274
|
+
if (type.includes("user")) return "user_message";
|
|
1275
|
+
if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
|
|
1276
|
+
if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
|
|
1237
1277
|
if (type.includes("plan") || type.includes("todo")) return "plan";
|
|
1238
|
-
if (type.includes("command")) return "
|
|
1278
|
+
if (type.includes("command")) return "command_execution";
|
|
1239
1279
|
if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
|
|
1240
|
-
return "
|
|
1241
|
-
}
|
|
1242
|
-
if (type.includes("mcp")) return "
|
|
1243
|
-
if (type.includes("dynamic tool")) return "
|
|
1244
|
-
if (type.includes("collab")) return "
|
|
1245
|
-
if (type.includes("web search")) return "
|
|
1246
|
-
if (type.includes("image")) return "
|
|
1247
|
-
if (type.includes("review entered")) return "
|
|
1248
|
-
if (type.includes("review exited")) return "
|
|
1249
|
-
if (type.includes("compact")) return "
|
|
1280
|
+
return "file_change";
|
|
1281
|
+
}
|
|
1282
|
+
if (type.includes("mcp")) return "mcp_tool_call";
|
|
1283
|
+
if (type.includes("dynamic tool")) return "dynamic_tool_call";
|
|
1284
|
+
if (type.includes("collab")) return "collab_agent_tool_call";
|
|
1285
|
+
if (type.includes("web search")) return "web_search";
|
|
1286
|
+
if (type.includes("image")) return "image_view";
|
|
1287
|
+
if (type.includes("review entered")) return "review_entered";
|
|
1288
|
+
if (type.includes("review exited")) return "review_exited";
|
|
1289
|
+
if (type.includes("compact")) return "context_compaction";
|
|
1250
1290
|
if (type.includes("error")) return "error";
|
|
1251
1291
|
return "unknown";
|
|
1252
1292
|
}
|
|
@@ -1255,6 +1295,11 @@ function itemIdOf(params) {
|
|
|
1255
1295
|
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1256
1296
|
return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
|
|
1257
1297
|
}
|
|
1298
|
+
function itemStatusOf(params) {
|
|
1299
|
+
if (!isRecord3(params)) return void 0;
|
|
1300
|
+
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1301
|
+
return asString(item["status"]) ?? void 0;
|
|
1302
|
+
}
|
|
1258
1303
|
function turnIdOf(params) {
|
|
1259
1304
|
if (!isRecord3(params)) return void 0;
|
|
1260
1305
|
return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
|
|
@@ -1287,45 +1332,45 @@ function toTurnStatus(status) {
|
|
|
1287
1332
|
return "completed";
|
|
1288
1333
|
}
|
|
1289
1334
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1292
|
-
if (
|
|
1335
|
+
function threadStateFromStatus(status) {
|
|
1336
|
+
const type = isRecord3(status) ? asString(status["type"]) : asString(status);
|
|
1337
|
+
if (type === "idle") return "idle";
|
|
1338
|
+
if (type === "systemError" || type === "error") return "error";
|
|
1293
1339
|
return "active";
|
|
1294
1340
|
}
|
|
1295
|
-
function
|
|
1296
|
-
|
|
1297
|
-
if (method.includes("reasoning")) return "reasoning";
|
|
1298
|
-
if (method.includes("plan")) return "plan";
|
|
1299
|
-
return void 0;
|
|
1341
|
+
function codexCompactionId(threadId, turnId) {
|
|
1342
|
+
return `codex:${threadId}:${turnId ?? "session"}`;
|
|
1300
1343
|
}
|
|
1301
|
-
function normalizeCodexTokenUsage(
|
|
1302
|
-
if (!isRecord3(
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const usedTokens =
|
|
1307
|
-
if (usedTokens ===
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1344
|
+
function normalizeCodexTokenUsage(tokenUsage) {
|
|
1345
|
+
if (!isRecord3(tokenUsage)) return void 0;
|
|
1346
|
+
const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
|
|
1347
|
+
const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
1348
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1349
|
+
const usedTokens = count(last?.["totalTokens"]);
|
|
1350
|
+
if (usedTokens === void 0 || usedTokens <= 0) return void 0;
|
|
1351
|
+
const totalProcessedTokens = count(total?.["totalTokens"]);
|
|
1352
|
+
const maxTokens = count(tokenUsage["modelContextWindow"]);
|
|
1353
|
+
const inputTokens = count(last?.["inputTokens"]);
|
|
1354
|
+
const cachedInputTokens = count(last?.["cachedInputTokens"]);
|
|
1355
|
+
const outputTokens = count(last?.["outputTokens"]);
|
|
1356
|
+
const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
|
|
1357
|
+
return {
|
|
1310
1358
|
usedTokens,
|
|
1359
|
+
lastUsedTokens: usedTokens,
|
|
1360
|
+
...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
|
|
1361
|
+
...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
|
|
1362
|
+
...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
|
|
1363
|
+
...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
|
|
1364
|
+
...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
|
|
1365
|
+
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
|
|
1311
1366
|
compactsAutomatically: true
|
|
1312
1367
|
};
|
|
1313
|
-
if (totalProcessed !== null && totalProcessed > usedTokens) {
|
|
1314
|
-
payload["totalProcessedTokens"] = totalProcessed;
|
|
1315
|
-
}
|
|
1316
|
-
const maxTokens = asNumber(params["modelContextWindow"]) ?? asNumber(usage["modelContextWindow"]);
|
|
1317
|
-
if (maxTokens !== null && maxTokens > 0) payload["maxTokens"] = maxTokens;
|
|
1318
|
-
const lastInput = asNumber(last["inputTokens"]) ?? asNumber(last["input_tokens"]);
|
|
1319
|
-
const lastOutput = asNumber(last["outputTokens"]) ?? asNumber(last["output_tokens"]);
|
|
1320
|
-
if (lastInput !== null) payload["lastInputTokens"] = lastInput;
|
|
1321
|
-
if (lastOutput !== null) payload["lastOutputTokens"] = lastOutput;
|
|
1322
|
-
return payload;
|
|
1323
1368
|
}
|
|
1324
1369
|
function turnCompletedControlFlow(params) {
|
|
1325
1370
|
const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
|
|
1326
|
-
const
|
|
1371
|
+
const state = toTurnStatus(asString(turn["status"]) ?? void 0);
|
|
1327
1372
|
const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
|
|
1328
|
-
return error ? {
|
|
1373
|
+
return error ? { state, errorMessage: error } : { state };
|
|
1329
1374
|
}
|
|
1330
1375
|
function toApplyPatchFileChanges(params) {
|
|
1331
1376
|
if (!isRecord3(params)) return [];
|
|
@@ -1343,10 +1388,33 @@ function toApplyPatchFileChanges(params) {
|
|
|
1343
1388
|
if (!path) continue;
|
|
1344
1389
|
const kind = (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase();
|
|
1345
1390
|
const movePath = asString(entry["move_path"]) ?? asString(entry["movePath"]);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1391
|
+
const diff = asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]);
|
|
1392
|
+
const content = asString(entry["content"]);
|
|
1393
|
+
if (kind === "add") {
|
|
1394
|
+
out.push({
|
|
1395
|
+
path,
|
|
1396
|
+
changeKind: "add",
|
|
1397
|
+
...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
|
|
1398
|
+
});
|
|
1399
|
+
} else if (kind === "delete") {
|
|
1400
|
+
out.push({
|
|
1401
|
+
path,
|
|
1402
|
+
changeKind: "delete",
|
|
1403
|
+
...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
|
|
1404
|
+
});
|
|
1405
|
+
} else if (movePath) {
|
|
1406
|
+
out.push({
|
|
1407
|
+
path,
|
|
1408
|
+
changeKind: "rename",
|
|
1409
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1410
|
+
});
|
|
1411
|
+
} else {
|
|
1412
|
+
out.push({
|
|
1413
|
+
path,
|
|
1414
|
+
changeKind: "modify",
|
|
1415
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1350
1418
|
}
|
|
1351
1419
|
return out;
|
|
1352
1420
|
}
|
|
@@ -1359,7 +1427,18 @@ function toUserInputQuestions(params) {
|
|
|
1359
1427
|
const id = asString(question["id"]);
|
|
1360
1428
|
const header = asString(question["header"]);
|
|
1361
1429
|
const text = asString(question["question"]);
|
|
1362
|
-
const
|
|
1430
|
+
const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
|
|
1431
|
+
const options = [];
|
|
1432
|
+
for (const option of rawOptions) {
|
|
1433
|
+
if (!isRecord3(option)) continue;
|
|
1434
|
+
const label = trimText(option["label"]);
|
|
1435
|
+
if (!label) continue;
|
|
1436
|
+
options.push({
|
|
1437
|
+
...asString(option["id"]) ? { id: asString(option["id"]) } : {},
|
|
1438
|
+
label,
|
|
1439
|
+
description: trimText(option["description"]) ?? ""
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1363
1442
|
if (!id || !header || !text || options.length === 0) return void 0;
|
|
1364
1443
|
mapped.push({
|
|
1365
1444
|
id,
|
|
@@ -1372,15 +1451,32 @@ function toUserInputQuestions(params) {
|
|
|
1372
1451
|
return mapped.length > 0 ? mapped : void 0;
|
|
1373
1452
|
}
|
|
1374
1453
|
function toCodexUserInputAnswers(answers) {
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1454
|
+
if (!isRecord3(answers)) return void 0;
|
|
1455
|
+
const out = {};
|
|
1456
|
+
for (const [questionId, value] of Object.entries(answers)) {
|
|
1457
|
+
if (typeof value === "string") {
|
|
1458
|
+
out[questionId] = { answers: [value] };
|
|
1459
|
+
continue;
|
|
1460
|
+
}
|
|
1461
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
1462
|
+
out[questionId] = { answers: [...value] };
|
|
1463
|
+
continue;
|
|
1464
|
+
}
|
|
1465
|
+
if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
|
|
1466
|
+
out[questionId] = { answers: [...value["answers"]] };
|
|
1467
|
+
continue;
|
|
1468
|
+
}
|
|
1469
|
+
return void 0;
|
|
1382
1470
|
}
|
|
1383
|
-
return
|
|
1471
|
+
return out;
|
|
1472
|
+
}
|
|
1473
|
+
function toCanonicalUserInputAnswers(answers) {
|
|
1474
|
+
return Object.fromEntries(
|
|
1475
|
+
Object.entries(answers).map(([questionId, value]) => [
|
|
1476
|
+
questionId,
|
|
1477
|
+
value.answers.length === 1 ? value.answers[0] : [...value.answers]
|
|
1478
|
+
])
|
|
1479
|
+
);
|
|
1384
1480
|
}
|
|
1385
1481
|
function pathLeaf(value) {
|
|
1386
1482
|
if (!value) return void 0;
|
|
@@ -1418,10 +1514,11 @@ function mapCollabAgentEvent(input) {
|
|
|
1418
1514
|
const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
|
|
1419
1515
|
const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
|
|
1420
1516
|
const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
|
|
1421
|
-
const
|
|
1517
|
+
const knownName = nickname ?? pathLeaf(agentPath);
|
|
1518
|
+
const title = knownName ?? agentThreadId;
|
|
1422
1519
|
const linkage = {
|
|
1423
1520
|
role,
|
|
1424
|
-
title,
|
|
1521
|
+
...knownName ? { title: knownName } : {},
|
|
1425
1522
|
...agentPath ? { agentPath } : {},
|
|
1426
1523
|
timelineBypass: true
|
|
1427
1524
|
};
|
|
@@ -1439,14 +1536,17 @@ function mapCollabAgentEvent(input) {
|
|
|
1439
1536
|
];
|
|
1440
1537
|
}
|
|
1441
1538
|
if (method === "collabAgent/progress" || method === "task.progress") {
|
|
1539
|
+
const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
|
|
1442
1540
|
return [
|
|
1443
1541
|
baseEvent({
|
|
1444
1542
|
type: "task.progress",
|
|
1445
1543
|
threadId,
|
|
1544
|
+
// 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
|
|
1446
1545
|
payload: {
|
|
1447
1546
|
taskId: agentThreadId,
|
|
1448
|
-
|
|
1449
|
-
|
|
1547
|
+
description: title,
|
|
1548
|
+
...summary ? { summary } : {},
|
|
1549
|
+
...linkage
|
|
1450
1550
|
},
|
|
1451
1551
|
method,
|
|
1452
1552
|
providerInstanceId: input.providerInstanceId
|
|
@@ -1456,37 +1556,53 @@ function mapCollabAgentEvent(input) {
|
|
|
1456
1556
|
if (method === "collabAgent/completed" || method === "task.completed") {
|
|
1457
1557
|
return [
|
|
1458
1558
|
baseEvent({
|
|
1459
|
-
type: "task.
|
|
1559
|
+
type: "task.updated",
|
|
1460
1560
|
threadId,
|
|
1461
|
-
payload: { taskId: agentThreadId, ...linkage },
|
|
1561
|
+
payload: { taskId: agentThreadId, status: "idle", ...linkage },
|
|
1462
1562
|
method,
|
|
1463
1563
|
providerInstanceId: input.providerInstanceId
|
|
1464
1564
|
})
|
|
1465
1565
|
];
|
|
1466
1566
|
}
|
|
1467
1567
|
if (method === "collabAgent/statusChanged") {
|
|
1568
|
+
const raw = asString(input.params["status"]);
|
|
1569
|
+
const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
|
|
1468
1570
|
return [
|
|
1469
1571
|
baseEvent({
|
|
1470
1572
|
type: "task.updated",
|
|
1471
1573
|
threadId,
|
|
1472
|
-
payload: {
|
|
1473
|
-
taskId: agentThreadId,
|
|
1474
|
-
...linkage,
|
|
1475
|
-
status: asString(input.params["status"]) ?? "running"
|
|
1476
|
-
},
|
|
1574
|
+
payload: { taskId: agentThreadId, status, ...linkage },
|
|
1477
1575
|
method,
|
|
1478
1576
|
providerInstanceId: input.providerInstanceId
|
|
1479
1577
|
})
|
|
1480
1578
|
];
|
|
1481
1579
|
}
|
|
1482
1580
|
if (method === "collabAgent/tokenUsage") {
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1581
|
+
const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
|
|
1582
|
+
const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
1583
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1584
|
+
const totalTokens = total ? count(total["totalTokens"]) : void 0;
|
|
1585
|
+
if (total === void 0 || totalTokens === void 0) return [];
|
|
1586
|
+
const typedUsage = { totalTokens };
|
|
1587
|
+
for (const key of [
|
|
1588
|
+
"inputTokens",
|
|
1589
|
+
"cachedInputTokens",
|
|
1590
|
+
"outputTokens",
|
|
1591
|
+
"reasoningOutputTokens"
|
|
1592
|
+
]) {
|
|
1593
|
+
const value = count(total[key]);
|
|
1594
|
+
if (value !== void 0) typedUsage[key] = value;
|
|
1595
|
+
}
|
|
1485
1596
|
return [
|
|
1486
1597
|
baseEvent({
|
|
1487
|
-
type: "
|
|
1488
|
-
threadId
|
|
1489
|
-
payload:
|
|
1598
|
+
type: "task.progress",
|
|
1599
|
+
threadId,
|
|
1600
|
+
payload: {
|
|
1601
|
+
taskId: agentThreadId,
|
|
1602
|
+
description: title,
|
|
1603
|
+
typedUsage,
|
|
1604
|
+
...linkage
|
|
1605
|
+
},
|
|
1490
1606
|
method,
|
|
1491
1607
|
providerInstanceId: input.providerInstanceId
|
|
1492
1608
|
})
|
|
@@ -1581,52 +1697,6 @@ function toMcpElicitationResponse(decision, params) {
|
|
|
1581
1697
|
...persist ? { _meta: { persist } } : {}
|
|
1582
1698
|
};
|
|
1583
1699
|
}
|
|
1584
|
-
function producedImages(params) {
|
|
1585
|
-
if (!isRecord3(params)) return [];
|
|
1586
|
-
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1587
|
-
const type = toCanonicalItemType(asString(item["type"]) ?? void 0);
|
|
1588
|
-
const events = [];
|
|
1589
|
-
const threadId = conversationIdFromNotification(params) ?? "";
|
|
1590
|
-
const emit = (payload) => {
|
|
1591
|
-
events.push(
|
|
1592
|
-
baseEvent({
|
|
1593
|
-
type: "assistant.attachment.produced",
|
|
1594
|
-
threadId,
|
|
1595
|
-
payload,
|
|
1596
|
-
itemId: asString(item["id"]) ?? void 0,
|
|
1597
|
-
turnId: turnIdOf(params),
|
|
1598
|
-
method: "item/completed"
|
|
1599
|
-
})
|
|
1600
|
-
);
|
|
1601
|
-
};
|
|
1602
|
-
if (type === "image") {
|
|
1603
|
-
const savedPath = asString(item["savedPath"]) ?? asString(item["path"]);
|
|
1604
|
-
if (savedPath) emit({ path: savedPath, mimeType: "image/png" });
|
|
1605
|
-
}
|
|
1606
|
-
const content = Array.isArray(item["content"]) ? item["content"] : [];
|
|
1607
|
-
for (const entry of content) {
|
|
1608
|
-
if (!isRecord3(entry)) continue;
|
|
1609
|
-
if (entry["type"] === "image" && typeof entry["data"] === "string") {
|
|
1610
|
-
const mimeType = asString(entry["mimeType"]) ?? "image/png";
|
|
1611
|
-
emit({
|
|
1612
|
-
mimeType,
|
|
1613
|
-
data: entry["data"],
|
|
1614
|
-
extension: inferImageExtension({ mimeType })
|
|
1615
|
-
});
|
|
1616
|
-
}
|
|
1617
|
-
if (entry["type"] === "inputImage" && typeof entry["imageUrl"] === "string") {
|
|
1618
|
-
const parsed = parseBase64DataUrl(entry["imageUrl"]);
|
|
1619
|
-
if (parsed) {
|
|
1620
|
-
emit({
|
|
1621
|
-
mimeType: parsed.mimeType,
|
|
1622
|
-
data: parsed.base64,
|
|
1623
|
-
extension: inferImageExtension({ mimeType: parsed.mimeType })
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
}
|
|
1627
|
-
}
|
|
1628
|
-
return events;
|
|
1629
|
-
}
|
|
1630
1700
|
function mapNotificationToEvents(input) {
|
|
1631
1701
|
const { method, params, threadId, providerInstanceId } = input;
|
|
1632
1702
|
if (method.startsWith("collabAgent/")) {
|
|
@@ -1678,58 +1748,121 @@ function mapNotificationToEvents(input) {
|
|
|
1678
1748
|
const itemId = itemIdOf(params);
|
|
1679
1749
|
const type = itemTypeOf(params);
|
|
1680
1750
|
switch (method) {
|
|
1681
|
-
case "thread/started":
|
|
1751
|
+
case "thread/started": {
|
|
1752
|
+
const startedThread = isRecord3(params) ? params["thread"] : void 0;
|
|
1753
|
+
const providerThreadId = isRecord3(startedThread) ? trimText(startedThread["id"]) : void 0;
|
|
1682
1754
|
return [
|
|
1683
1755
|
baseEvent({
|
|
1684
1756
|
type: "thread.started",
|
|
1685
1757
|
threadId,
|
|
1686
|
-
payload:
|
|
1758
|
+
payload: providerThreadId ? { providerThreadId } : {},
|
|
1687
1759
|
method,
|
|
1688
1760
|
providerInstanceId
|
|
1689
1761
|
})
|
|
1690
1762
|
];
|
|
1763
|
+
}
|
|
1691
1764
|
case "thread/status/changed": {
|
|
1692
|
-
const status = isRecord3(params) ?
|
|
1765
|
+
const status = isRecord3(params) ? params["status"] : void 0;
|
|
1693
1766
|
return [
|
|
1694
1767
|
baseEvent({
|
|
1695
1768
|
type: "thread.state.changed",
|
|
1696
1769
|
threadId,
|
|
1697
|
-
payload: {
|
|
1770
|
+
payload: {
|
|
1771
|
+
state: threadStateFromStatus(status),
|
|
1772
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1773
|
+
},
|
|
1698
1774
|
method,
|
|
1699
1775
|
providerInstanceId
|
|
1700
1776
|
})
|
|
1701
1777
|
];
|
|
1702
1778
|
}
|
|
1703
|
-
case "thread/
|
|
1779
|
+
case "thread/archived":
|
|
1780
|
+
case "thread/unarchived":
|
|
1781
|
+
return [
|
|
1782
|
+
baseEvent({
|
|
1783
|
+
type: "thread.state.changed",
|
|
1784
|
+
threadId,
|
|
1785
|
+
payload: {
|
|
1786
|
+
state: method === "thread/archived" ? "archived" : "active",
|
|
1787
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1788
|
+
},
|
|
1789
|
+
method,
|
|
1790
|
+
providerInstanceId
|
|
1791
|
+
})
|
|
1792
|
+
];
|
|
1793
|
+
case "thread/closed":
|
|
1794
|
+
return [
|
|
1795
|
+
baseEvent({
|
|
1796
|
+
type: "thread.state.changed",
|
|
1797
|
+
threadId,
|
|
1798
|
+
payload: {
|
|
1799
|
+
state: "closed",
|
|
1800
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1801
|
+
},
|
|
1802
|
+
method,
|
|
1803
|
+
providerInstanceId
|
|
1804
|
+
})
|
|
1805
|
+
];
|
|
1806
|
+
case "thread/compacted":
|
|
1807
|
+
return [
|
|
1808
|
+
baseEvent({
|
|
1809
|
+
type: "thread.state.changed",
|
|
1810
|
+
threadId,
|
|
1811
|
+
payload: {
|
|
1812
|
+
state: "compacted",
|
|
1813
|
+
compaction: {
|
|
1814
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1815
|
+
// Codex 不报触发方,手动压缩也走同一条通知。
|
|
1816
|
+
trigger: "automatic"
|
|
1817
|
+
},
|
|
1818
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1819
|
+
},
|
|
1820
|
+
method,
|
|
1821
|
+
providerInstanceId
|
|
1822
|
+
})
|
|
1823
|
+
];
|
|
1824
|
+
case "thread/name/updated": {
|
|
1825
|
+
const threadName = isRecord3(params) ? trimText(params["threadName"]) ?? trimText(params["name"]) : void 0;
|
|
1826
|
+
const nativeThreadId = isRecord3(params) ? asString(params["threadId"]) : void 0;
|
|
1704
1827
|
return [
|
|
1705
1828
|
baseEvent({
|
|
1706
1829
|
type: "thread.metadata.updated",
|
|
1707
1830
|
threadId,
|
|
1708
|
-
payload: {
|
|
1831
|
+
payload: {
|
|
1832
|
+
...threadName ? { name: threadName } : {},
|
|
1833
|
+
...nativeThreadId || threadName !== void 0 ? {
|
|
1834
|
+
metadata: {
|
|
1835
|
+
...nativeThreadId ? { threadId: nativeThreadId } : {},
|
|
1836
|
+
...threadName !== void 0 ? { threadName } : {}
|
|
1837
|
+
}
|
|
1838
|
+
} : {}
|
|
1839
|
+
},
|
|
1709
1840
|
method,
|
|
1710
1841
|
providerInstanceId
|
|
1711
1842
|
})
|
|
1712
1843
|
];
|
|
1844
|
+
}
|
|
1713
1845
|
case "thread/tokenUsage/updated": {
|
|
1714
|
-
const usage = normalizeCodexTokenUsage(
|
|
1846
|
+
const usage = normalizeCodexTokenUsage(
|
|
1847
|
+
isRecord3(params) ? params["tokenUsage"] ?? params["usage"] : void 0
|
|
1848
|
+
);
|
|
1715
1849
|
if (!usage) return [];
|
|
1716
1850
|
return [
|
|
1717
1851
|
baseEvent({
|
|
1718
1852
|
type: "thread.token-usage.updated",
|
|
1719
1853
|
threadId,
|
|
1720
|
-
payload: usage,
|
|
1854
|
+
payload: { usage },
|
|
1721
1855
|
method,
|
|
1722
1856
|
providerInstanceId
|
|
1723
1857
|
})
|
|
1724
1858
|
];
|
|
1725
1859
|
}
|
|
1726
|
-
case "thread/closed":
|
|
1727
1860
|
case "session/closed":
|
|
1728
1861
|
return [
|
|
1729
1862
|
baseEvent({
|
|
1730
1863
|
type: "session.exited",
|
|
1731
1864
|
threadId,
|
|
1732
|
-
payload: {},
|
|
1865
|
+
payload: { exitKind: "graceful" },
|
|
1733
1866
|
method,
|
|
1734
1867
|
providerInstanceId
|
|
1735
1868
|
})
|
|
@@ -1740,43 +1873,85 @@ function mapNotificationToEvents(input) {
|
|
|
1740
1873
|
type: "turn.started",
|
|
1741
1874
|
threadId,
|
|
1742
1875
|
turnId,
|
|
1743
|
-
payload: {
|
|
1876
|
+
payload: {},
|
|
1744
1877
|
method,
|
|
1745
1878
|
providerInstanceId
|
|
1746
1879
|
})
|
|
1747
1880
|
];
|
|
1748
|
-
case "turn/completed":
|
|
1749
|
-
const flow = turnCompletedControlFlow(params);
|
|
1750
|
-
const typeName = flow.status === "cancelled" || flow.status === "interrupted" ? "turn.aborted" : "turn.completed";
|
|
1881
|
+
case "turn/completed":
|
|
1751
1882
|
return [
|
|
1752
1883
|
baseEvent({
|
|
1753
|
-
type:
|
|
1884
|
+
type: "turn.completed",
|
|
1754
1885
|
threadId,
|
|
1755
1886
|
turnId,
|
|
1756
|
-
payload:
|
|
1887
|
+
payload: turnCompletedControlFlow(params),
|
|
1888
|
+
method,
|
|
1889
|
+
providerInstanceId
|
|
1890
|
+
})
|
|
1891
|
+
];
|
|
1892
|
+
case "turn/aborted":
|
|
1893
|
+
return [
|
|
1894
|
+
baseEvent({
|
|
1895
|
+
type: "turn.aborted",
|
|
1896
|
+
threadId,
|
|
1897
|
+
turnId,
|
|
1898
|
+
payload: {
|
|
1899
|
+
reason: (isRecord3(params) ? trimText(params["reason"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Turn aborted"
|
|
1900
|
+
},
|
|
1757
1901
|
method,
|
|
1758
1902
|
providerInstanceId
|
|
1759
1903
|
})
|
|
1760
1904
|
];
|
|
1761
|
-
}
|
|
1762
1905
|
case "turn/plan":
|
|
1906
|
+
case "turn/plan/updated": {
|
|
1907
|
+
const rawPlan = isRecord3(params) && Array.isArray(params["plan"]) ? params["plan"] : [];
|
|
1908
|
+
const plan = rawPlan.filter(isRecord3).map((step) => ({
|
|
1909
|
+
step: trimText(step["step"]) ?? "step",
|
|
1910
|
+
status: step["status"] === "completed" || step["status"] === "inProgress" ? step["status"] : "pending"
|
|
1911
|
+
}));
|
|
1912
|
+
const explanation = isRecord3(params) ? trimText(params["explanation"]) : void 0;
|
|
1763
1913
|
return [
|
|
1764
1914
|
baseEvent({
|
|
1765
1915
|
type: "turn.plan.updated",
|
|
1766
1916
|
threadId,
|
|
1767
1917
|
turnId,
|
|
1768
|
-
payload:
|
|
1918
|
+
payload: {
|
|
1919
|
+
...explanation ? { explanation } : {},
|
|
1920
|
+
plan
|
|
1921
|
+
},
|
|
1922
|
+
method,
|
|
1923
|
+
providerInstanceId
|
|
1924
|
+
})
|
|
1925
|
+
];
|
|
1926
|
+
}
|
|
1927
|
+
case "turn/diff/updated": {
|
|
1928
|
+
const unifiedDiff = isRecord3(params) ? asString(params["diff"]) : void 0;
|
|
1929
|
+
if (unifiedDiff === void 0) return [];
|
|
1930
|
+
return [
|
|
1931
|
+
baseEvent({
|
|
1932
|
+
type: "turn.diff.updated",
|
|
1933
|
+
threadId,
|
|
1934
|
+
turnId,
|
|
1935
|
+
payload: { unifiedDiff },
|
|
1769
1936
|
method,
|
|
1770
1937
|
providerInstanceId
|
|
1771
1938
|
})
|
|
1772
1939
|
];
|
|
1940
|
+
}
|
|
1773
1941
|
case "item/started": {
|
|
1774
1942
|
if (type && normalizeItemType(type).includes("compact")) {
|
|
1775
1943
|
return [
|
|
1776
1944
|
baseEvent({
|
|
1777
1945
|
type: "thread.state.changed",
|
|
1778
1946
|
threadId,
|
|
1779
|
-
payload: {
|
|
1947
|
+
payload: {
|
|
1948
|
+
state: "compacting",
|
|
1949
|
+
compaction: {
|
|
1950
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1951
|
+
trigger: "automatic"
|
|
1952
|
+
},
|
|
1953
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1954
|
+
},
|
|
1780
1955
|
turnId,
|
|
1781
1956
|
itemId,
|
|
1782
1957
|
method,
|
|
@@ -1784,6 +1959,8 @@ function mapNotificationToEvents(input) {
|
|
|
1784
1959
|
})
|
|
1785
1960
|
];
|
|
1786
1961
|
}
|
|
1962
|
+
const canonical = toCanonicalItemType(type);
|
|
1963
|
+
if (canonical === "unknown") return [];
|
|
1787
1964
|
return [
|
|
1788
1965
|
baseEvent({
|
|
1789
1966
|
type: "item.started",
|
|
@@ -1791,9 +1968,11 @@ function mapNotificationToEvents(input) {
|
|
|
1791
1968
|
turnId,
|
|
1792
1969
|
itemId,
|
|
1793
1970
|
payload: {
|
|
1794
|
-
itemType:
|
|
1795
|
-
|
|
1796
|
-
|
|
1971
|
+
itemType: canonical,
|
|
1972
|
+
status: "inProgress",
|
|
1973
|
+
...itemTitle(params) ? { title: itemTitle(params) } : {},
|
|
1974
|
+
...itemDetail(params) ? { detail: itemDetail(params) } : {},
|
|
1975
|
+
...params !== void 0 ? { data: params } : {}
|
|
1797
1976
|
},
|
|
1798
1977
|
method,
|
|
1799
1978
|
providerInstanceId
|
|
@@ -1806,7 +1985,14 @@ function mapNotificationToEvents(input) {
|
|
|
1806
1985
|
baseEvent({
|
|
1807
1986
|
type: "thread.state.changed",
|
|
1808
1987
|
threadId,
|
|
1809
|
-
payload: {
|
|
1988
|
+
payload: {
|
|
1989
|
+
state: "compacted",
|
|
1990
|
+
compaction: {
|
|
1991
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1992
|
+
trigger: "automatic"
|
|
1993
|
+
},
|
|
1994
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1995
|
+
},
|
|
1810
1996
|
turnId,
|
|
1811
1997
|
itemId,
|
|
1812
1998
|
method,
|
|
@@ -1815,18 +2001,24 @@ function mapNotificationToEvents(input) {
|
|
|
1815
2001
|
];
|
|
1816
2002
|
}
|
|
1817
2003
|
if (type && normalizeItemType(type).includes("plan")) {
|
|
2004
|
+
const planMarkdown = itemDetail(params);
|
|
2005
|
+
if (!planMarkdown) return [];
|
|
1818
2006
|
return [
|
|
1819
2007
|
baseEvent({
|
|
1820
2008
|
type: "turn.proposed.completed",
|
|
1821
2009
|
threadId,
|
|
1822
2010
|
turnId,
|
|
1823
2011
|
itemId,
|
|
1824
|
-
payload:
|
|
2012
|
+
payload: { planMarkdown },
|
|
1825
2013
|
method,
|
|
1826
2014
|
providerInstanceId
|
|
1827
2015
|
})
|
|
1828
2016
|
];
|
|
1829
2017
|
}
|
|
2018
|
+
const canonical = toCanonicalItemType(type);
|
|
2019
|
+
if (canonical === "unknown") return [];
|
|
2020
|
+
const rawStatus = itemStatusOf(params);
|
|
2021
|
+
const status = rawStatus === "failed" || rawStatus === "declined" ? rawStatus : "completed";
|
|
1830
2022
|
return [
|
|
1831
2023
|
baseEvent({
|
|
1832
2024
|
type: "item.completed",
|
|
@@ -1834,45 +2026,62 @@ function mapNotificationToEvents(input) {
|
|
|
1834
2026
|
turnId,
|
|
1835
2027
|
itemId,
|
|
1836
2028
|
payload: {
|
|
1837
|
-
itemType:
|
|
1838
|
-
|
|
1839
|
-
|
|
2029
|
+
itemType: canonical,
|
|
2030
|
+
status,
|
|
2031
|
+
...itemTitle(params) ? { title: itemTitle(params) } : {},
|
|
2032
|
+
...itemDetail(params) ? { detail: itemDetail(params) } : {},
|
|
2033
|
+
...params !== void 0 ? { data: params } : {}
|
|
1840
2034
|
},
|
|
1841
2035
|
method,
|
|
1842
2036
|
providerInstanceId
|
|
1843
|
-
})
|
|
1844
|
-
...producedImages(params).map((event) => ({ ...event, threadId }))
|
|
2037
|
+
})
|
|
1845
2038
|
];
|
|
1846
2039
|
}
|
|
1847
|
-
case "item/plan/delta":
|
|
2040
|
+
case "item/plan/delta": {
|
|
2041
|
+
const delta = isRecord3(params) ? asString(params["delta"]) : void 0;
|
|
2042
|
+
if (!delta) return [];
|
|
1848
2043
|
return [
|
|
1849
2044
|
baseEvent({
|
|
1850
2045
|
type: "turn.proposed.delta",
|
|
1851
2046
|
threadId,
|
|
1852
2047
|
turnId,
|
|
1853
2048
|
itemId,
|
|
1854
|
-
payload:
|
|
2049
|
+
payload: { delta },
|
|
1855
2050
|
method,
|
|
1856
2051
|
providerInstanceId
|
|
1857
2052
|
})
|
|
1858
2053
|
];
|
|
2054
|
+
}
|
|
1859
2055
|
case "item/agentMessage/delta":
|
|
1860
|
-
case "item/reasoning/
|
|
1861
|
-
case "item/reasoning/
|
|
2056
|
+
case "item/reasoning/textDelta":
|
|
2057
|
+
case "item/reasoning/summaryTextDelta":
|
|
2058
|
+
case "item/commandExecution/outputDelta":
|
|
2059
|
+
case "item/fileChange/outputDelta": {
|
|
1862
2060
|
const delta = isRecord3(params) ? asString(params["delta"]) ?? asString(params["text"]) ?? "" : "";
|
|
2061
|
+
if (delta.length === 0) return [];
|
|
2062
|
+
const streamKind = method === "item/agentMessage/delta" ? "assistant_text" : method === "item/reasoning/textDelta" ? "reasoning_text" : method === "item/reasoning/summaryTextDelta" ? "reasoning_summary_text" : method === "item/commandExecution/outputDelta" ? "command_output" : "file_change_output";
|
|
2063
|
+
const summaryIndex = method === "item/reasoning/summaryTextDelta" && isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
|
|
2064
|
+
const contentIndex = method === "item/reasoning/textDelta" && isRecord3(params) ? asNumber(params["contentIndex"]) : null;
|
|
1863
2065
|
return [
|
|
1864
2066
|
baseEvent({
|
|
1865
2067
|
type: "content.delta",
|
|
1866
2068
|
threadId,
|
|
1867
2069
|
turnId,
|
|
1868
2070
|
itemId,
|
|
1869
|
-
payload: {
|
|
2071
|
+
payload: {
|
|
2072
|
+
streamKind,
|
|
2073
|
+
delta,
|
|
2074
|
+
...summaryIndex !== null ? { summaryIndex } : {},
|
|
2075
|
+
...contentIndex !== null ? { contentIndex } : {}
|
|
2076
|
+
},
|
|
1870
2077
|
method,
|
|
1871
2078
|
providerInstanceId
|
|
1872
2079
|
})
|
|
1873
2080
|
];
|
|
1874
2081
|
}
|
|
1875
|
-
case "item/reasoning/summaryPartAdded":
|
|
2082
|
+
case "item/reasoning/summaryPartAdded": {
|
|
2083
|
+
const summaryIndex = isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
|
|
2084
|
+
if (summaryIndex === null) return [];
|
|
1876
2085
|
return [
|
|
1877
2086
|
baseEvent({
|
|
1878
2087
|
type: "content.delta",
|
|
@@ -1880,59 +2089,176 @@ function mapNotificationToEvents(input) {
|
|
|
1880
2089
|
turnId,
|
|
1881
2090
|
itemId,
|
|
1882
2091
|
payload: {
|
|
1883
|
-
|
|
2092
|
+
streamKind: "reasoning_summary_text",
|
|
1884
2093
|
delta: "",
|
|
1885
|
-
summaryIndex
|
|
2094
|
+
summaryIndex
|
|
2095
|
+
},
|
|
2096
|
+
method,
|
|
2097
|
+
providerInstanceId
|
|
2098
|
+
})
|
|
2099
|
+
];
|
|
2100
|
+
}
|
|
2101
|
+
case "item/mcpToolCall/progress": {
|
|
2102
|
+
const summary = isRecord3(params) ? trimText(params["message"]) : void 0;
|
|
2103
|
+
if (!summary) return [];
|
|
2104
|
+
return [
|
|
2105
|
+
baseEvent({
|
|
2106
|
+
type: "tool.progress",
|
|
2107
|
+
threadId,
|
|
2108
|
+
turnId,
|
|
2109
|
+
itemId,
|
|
2110
|
+
payload: { summary },
|
|
2111
|
+
method,
|
|
2112
|
+
providerInstanceId
|
|
2113
|
+
})
|
|
2114
|
+
];
|
|
2115
|
+
}
|
|
2116
|
+
case "item/commandExecution/terminalInteraction":
|
|
2117
|
+
return [
|
|
2118
|
+
baseEvent({
|
|
2119
|
+
type: "item.updated",
|
|
2120
|
+
threadId,
|
|
2121
|
+
turnId,
|
|
2122
|
+
itemId,
|
|
2123
|
+
payload: {
|
|
2124
|
+
itemType: "command_execution",
|
|
2125
|
+
...params !== void 0 ? { data: params } : {}
|
|
1886
2126
|
},
|
|
1887
2127
|
method,
|
|
1888
2128
|
providerInstanceId
|
|
1889
2129
|
})
|
|
1890
2130
|
];
|
|
1891
2131
|
case "account/updated":
|
|
2132
|
+
return [
|
|
2133
|
+
baseEvent({
|
|
2134
|
+
type: "account.updated",
|
|
2135
|
+
threadId,
|
|
2136
|
+
payload: { account: params ?? {} },
|
|
2137
|
+
method,
|
|
2138
|
+
providerInstanceId
|
|
2139
|
+
})
|
|
2140
|
+
];
|
|
1892
2141
|
case "account/rateLimits/updated":
|
|
1893
2142
|
return [
|
|
1894
2143
|
baseEvent({
|
|
1895
|
-
type:
|
|
2144
|
+
type: "account.rate-limits.updated",
|
|
1896
2145
|
threadId,
|
|
1897
|
-
payload:
|
|
2146
|
+
payload: {
|
|
2147
|
+
rateLimits: isRecord3(params) && "rateLimits" in params ? params["rateLimits"] : params ?? {}
|
|
2148
|
+
},
|
|
1898
2149
|
method,
|
|
1899
2150
|
providerInstanceId
|
|
1900
2151
|
})
|
|
1901
2152
|
];
|
|
1902
|
-
case "model/rerouted":
|
|
2153
|
+
case "model/rerouted": {
|
|
2154
|
+
const fromModel = isRecord3(params) ? trimText(params["fromModel"]) : void 0;
|
|
2155
|
+
const toModel = isRecord3(params) ? trimText(params["toModel"]) : void 0;
|
|
2156
|
+
const reason = isRecord3(params) ? trimText(params["reason"]) : void 0;
|
|
2157
|
+
if (!fromModel || !toModel || !reason) return [];
|
|
1903
2158
|
return [
|
|
1904
2159
|
baseEvent({
|
|
1905
2160
|
type: "model.rerouted",
|
|
1906
2161
|
threadId,
|
|
1907
|
-
|
|
2162
|
+
turnId,
|
|
2163
|
+
payload: { fromModel, toModel, reason },
|
|
1908
2164
|
method,
|
|
1909
2165
|
providerInstanceId
|
|
1910
2166
|
})
|
|
1911
2167
|
];
|
|
1912
|
-
|
|
2168
|
+
}
|
|
2169
|
+
case "configWarning":
|
|
2170
|
+
case "deprecationNotice": {
|
|
2171
|
+
const summary = isRecord3(params) ? trimText(params["summary"]) : void 0;
|
|
2172
|
+
if (!summary) return [];
|
|
2173
|
+
const details = isRecord3(params) ? trimText(params["details"]) : void 0;
|
|
1913
2174
|
return [
|
|
1914
2175
|
baseEvent({
|
|
1915
|
-
type: "config.warning",
|
|
2176
|
+
type: method === "configWarning" ? "config.warning" : "deprecation.notice",
|
|
1916
2177
|
threadId,
|
|
1917
|
-
payload:
|
|
2178
|
+
payload: {
|
|
2179
|
+
summary,
|
|
2180
|
+
...details ? { details } : {},
|
|
2181
|
+
...method === "configWarning" && isRecord3(params) ? {
|
|
2182
|
+
...trimText(params["path"]) ? { path: trimText(params["path"]) } : {},
|
|
2183
|
+
...params["range"] !== void 0 && params["range"] !== null ? { range: params["range"] } : {}
|
|
2184
|
+
} : {}
|
|
2185
|
+
},
|
|
1918
2186
|
method,
|
|
1919
2187
|
providerInstanceId
|
|
1920
2188
|
})
|
|
1921
2189
|
];
|
|
1922
|
-
|
|
2190
|
+
}
|
|
2191
|
+
case "mcpServer/oauthLogin/completed": {
|
|
2192
|
+
const success = isRecord3(params) ? params["success"] : void 0;
|
|
2193
|
+
if (typeof success !== "boolean") return [];
|
|
2194
|
+
const name = isRecord3(params) ? trimText(params["name"]) : void 0;
|
|
2195
|
+
const error = isRecord3(params) ? trimText(params["error"]) : void 0;
|
|
1923
2196
|
return [
|
|
1924
2197
|
baseEvent({
|
|
1925
2198
|
type: "mcp.oauth.completed",
|
|
1926
2199
|
threadId,
|
|
1927
|
-
payload:
|
|
2200
|
+
payload: {
|
|
2201
|
+
success,
|
|
2202
|
+
...name ? { name } : {},
|
|
2203
|
+
...error ? { error } : {}
|
|
2204
|
+
},
|
|
1928
2205
|
method,
|
|
1929
2206
|
providerInstanceId
|
|
1930
2207
|
})
|
|
1931
2208
|
];
|
|
2209
|
+
}
|
|
1932
2210
|
default:
|
|
1933
2211
|
return [];
|
|
1934
2212
|
}
|
|
1935
2213
|
}
|
|
2214
|
+
function toRequestTypeFromKind(kind) {
|
|
2215
|
+
switch (kind) {
|
|
2216
|
+
case "command":
|
|
2217
|
+
return "command_execution_approval";
|
|
2218
|
+
case "file-read":
|
|
2219
|
+
return "file_read_approval";
|
|
2220
|
+
case "file-change":
|
|
2221
|
+
return "file_change_approval";
|
|
2222
|
+
case "apply-patch":
|
|
2223
|
+
return "apply_patch_approval";
|
|
2224
|
+
case "mcp-elicitation":
|
|
2225
|
+
return "mcp_elicitation_approval";
|
|
2226
|
+
default:
|
|
2227
|
+
return "unknown";
|
|
2228
|
+
}
|
|
2229
|
+
}
|
|
2230
|
+
function toRequestTypeFromMethod(method) {
|
|
2231
|
+
switch (method) {
|
|
2232
|
+
case "item/commandExecution/requestApproval":
|
|
2233
|
+
return "command_execution_approval";
|
|
2234
|
+
case "execCommandApproval":
|
|
2235
|
+
return "exec_command_approval";
|
|
2236
|
+
case "item/fileRead/requestApproval":
|
|
2237
|
+
return "file_read_approval";
|
|
2238
|
+
case "item/fileChange/requestApproval":
|
|
2239
|
+
return "file_change_approval";
|
|
2240
|
+
case "applyPatchApproval":
|
|
2241
|
+
return "apply_patch_approval";
|
|
2242
|
+
case "mcpServer/elicitation/request":
|
|
2243
|
+
return "mcp_elicitation_approval";
|
|
2244
|
+
case "item/tool/requestUserInput":
|
|
2245
|
+
return "tool_user_input";
|
|
2246
|
+
case "item/tool/call":
|
|
2247
|
+
return "dynamic_tool_call";
|
|
2248
|
+
default:
|
|
2249
|
+
return "unknown";
|
|
2250
|
+
}
|
|
2251
|
+
}
|
|
2252
|
+
function elicitationOptions(params) {
|
|
2253
|
+
const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
|
|
2254
|
+
return [
|
|
2255
|
+
{ decision: "cancel", label: "Cancel" },
|
|
2256
|
+
{ decision: "decline", label: "Decline" },
|
|
2257
|
+
...persist === "session" ? [{ decision: "acceptForSession", label: "Always allow this session" }] : [],
|
|
2258
|
+
...persist === "always" ? [{ decision: "acceptAlways", label: "Always allow" }] : [],
|
|
2259
|
+
{ decision: "accept", label: "Approve" }
|
|
2260
|
+
];
|
|
2261
|
+
}
|
|
1936
2262
|
function mapServerRequestToEvents(input) {
|
|
1937
2263
|
const requestId = String(input.id);
|
|
1938
2264
|
if (input.method === "item/tool/requestUserInput") {
|
|
@@ -1950,18 +2276,44 @@ function mapServerRequestToEvents(input) {
|
|
|
1950
2276
|
})
|
|
1951
2277
|
];
|
|
1952
2278
|
}
|
|
1953
|
-
const
|
|
1954
|
-
const
|
|
2279
|
+
const requestType = toRequestTypeFromMethod(input.method);
|
|
2280
|
+
const payload = {
|
|
2281
|
+
requestType,
|
|
2282
|
+
// Codex 的审批回复只有一个枚举,理由随下一条 prompt 送达。
|
|
2283
|
+
declineReasonRelay: "queued"
|
|
2284
|
+
};
|
|
2285
|
+
if (input.method === "mcpServer/elicitation/request") {
|
|
2286
|
+
const described = describeMcpElicitation(input.params);
|
|
2287
|
+
const appName = described.appName ?? described.serverName ?? void 0;
|
|
2288
|
+
payload["options"] = elicitationOptions(input.params);
|
|
2289
|
+
if (appName) payload["appName"] = appName;
|
|
2290
|
+
if (described.message) payload["detail"] = described.message;
|
|
2291
|
+
payload["args"] = input.params;
|
|
2292
|
+
} else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
|
|
2293
|
+
const described = describeFileChangeApproval(input.params);
|
|
2294
|
+
if (described.reason) payload["detail"] = described.reason;
|
|
2295
|
+
if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
|
|
2296
|
+
payload["args"] = {
|
|
2297
|
+
...described.callId ? { callId: described.callId } : {},
|
|
2298
|
+
...described.reason ? { reason: described.reason } : {},
|
|
2299
|
+
...described.grantRoot ? { grantRoot: described.grantRoot } : {}
|
|
2300
|
+
};
|
|
2301
|
+
} else {
|
|
2302
|
+
const described = describeCommandApproval(input.params);
|
|
2303
|
+
const detail = described.command || described.reason;
|
|
2304
|
+
if (detail) payload["detail"] = detail;
|
|
2305
|
+
payload["args"] = {
|
|
2306
|
+
command: described.command,
|
|
2307
|
+
...described.cwd ? { cwd: described.cwd } : {},
|
|
2308
|
+
...described.reason ? { reason: described.reason } : {}
|
|
2309
|
+
};
|
|
2310
|
+
}
|
|
1955
2311
|
return [
|
|
1956
2312
|
baseEvent({
|
|
1957
2313
|
type: "request.opened",
|
|
1958
2314
|
threadId: input.threadId,
|
|
1959
2315
|
requestId,
|
|
1960
|
-
payload
|
|
1961
|
-
kind,
|
|
1962
|
-
args,
|
|
1963
|
-
declineReasonRelay: "queued"
|
|
1964
|
-
},
|
|
2316
|
+
payload,
|
|
1965
2317
|
method: input.method,
|
|
1966
2318
|
source: "codex.app-server.request",
|
|
1967
2319
|
providerInstanceId: input.providerInstanceId
|
|
@@ -1990,12 +2342,15 @@ var CodexSessionRuntime = class {
|
|
|
1990
2342
|
activeTurnId;
|
|
1991
2343
|
model;
|
|
1992
2344
|
runtimeMode;
|
|
2345
|
+
/** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
|
|
2346
|
+
nativeModeId;
|
|
1993
2347
|
interactionMode;
|
|
1994
2348
|
closed = false;
|
|
1995
2349
|
closeCompleted = false;
|
|
1996
2350
|
turns = [];
|
|
1997
2351
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
1998
2352
|
pendingUserInputs = /* @__PURE__ */ new Map();
|
|
2353
|
+
requestKinds = /* @__PURE__ */ new Map();
|
|
1999
2354
|
writeAcks = /* @__PURE__ */ new Map();
|
|
2000
2355
|
grants;
|
|
2001
2356
|
declineRelay;
|
|
@@ -2007,6 +2362,16 @@ var CodexSessionRuntime = class {
|
|
|
2007
2362
|
host;
|
|
2008
2363
|
rpc;
|
|
2009
2364
|
emit;
|
|
2365
|
+
/** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
|
|
2366
|
+
#threadConfig() {
|
|
2367
|
+
return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
|
|
2368
|
+
}
|
|
2369
|
+
/** 同上,沙箱策略那一位。 */
|
|
2370
|
+
#turnSandboxPolicy() {
|
|
2371
|
+
return codexTurnSandboxPolicyForNativeMode(
|
|
2372
|
+
codexNativeModeFor(this.runtimeMode, this.nativeModeId)
|
|
2373
|
+
);
|
|
2374
|
+
}
|
|
2010
2375
|
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
|
|
2011
2376
|
this.host = host;
|
|
2012
2377
|
this.rpc = rpc;
|
|
@@ -2014,6 +2379,7 @@ var CodexSessionRuntime = class {
|
|
|
2014
2379
|
this.threadId = input.threadId;
|
|
2015
2380
|
this.cwd = cwd;
|
|
2016
2381
|
this.runtimeMode = input.runtimeMode;
|
|
2382
|
+
this.nativeModeId = input.nativeModeId;
|
|
2017
2383
|
this.appServerArgs = appServerArgs;
|
|
2018
2384
|
this.grants = grants ?? createApprovalGrantStore();
|
|
2019
2385
|
this.declineRelay = declineRelay ?? createDeclineReasonRelay();
|
|
@@ -2023,6 +2389,7 @@ var CodexSessionRuntime = class {
|
|
|
2023
2389
|
providerInstanceId: host.instanceId,
|
|
2024
2390
|
status: "idle",
|
|
2025
2391
|
runtimeMode: input.runtimeMode,
|
|
2392
|
+
nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
|
|
2026
2393
|
cwd,
|
|
2027
2394
|
threadId: input.threadId,
|
|
2028
2395
|
createdAt,
|
|
@@ -2102,6 +2469,24 @@ var CodexSessionRuntime = class {
|
|
|
2102
2469
|
this.dispatchMapped(method, params);
|
|
2103
2470
|
}
|
|
2104
2471
|
dispatchMapped(method, params) {
|
|
2472
|
+
if (method === "serverRequest/resolved") {
|
|
2473
|
+
const resolvedId = (isRecord3(params) ? asString(params["requestId"]) : null) ?? void 0;
|
|
2474
|
+
if (resolvedId !== void 0) {
|
|
2475
|
+
this.emit({
|
|
2476
|
+
eventId: `codex-req-resolved-${resolvedId}`,
|
|
2477
|
+
provider: CODEX_DRIVER_KIND,
|
|
2478
|
+
threadId: this.threadId,
|
|
2479
|
+
createdAt: nowIso(),
|
|
2480
|
+
type: "request.resolved",
|
|
2481
|
+
requestId: resolvedId,
|
|
2482
|
+
payload: {
|
|
2483
|
+
requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
|
|
2484
|
+
resolution: params
|
|
2485
|
+
}
|
|
2486
|
+
});
|
|
2487
|
+
}
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2105
2490
|
for (const event of mapNotificationToEvents({
|
|
2106
2491
|
method,
|
|
2107
2492
|
params,
|
|
@@ -2280,6 +2665,7 @@ var CodexSessionRuntime = class {
|
|
|
2280
2665
|
threadId: this.threadId,
|
|
2281
2666
|
providerInstanceId: this.host.instanceId
|
|
2282
2667
|
});
|
|
2668
|
+
this.requestKinds.set(requestId, kind);
|
|
2283
2669
|
await new Promise((resolve2) => {
|
|
2284
2670
|
this.pendingApprovals.set(requestId, {
|
|
2285
2671
|
jsonRpcId: id,
|
|
@@ -2319,14 +2705,22 @@ var CodexSessionRuntime = class {
|
|
|
2319
2705
|
createdAt: nowIso(),
|
|
2320
2706
|
type: "request.resolved",
|
|
2321
2707
|
requestId,
|
|
2322
|
-
|
|
2708
|
+
// requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
|
|
2709
|
+
payload: {
|
|
2710
|
+
requestType: toRequestTypeFromKind(pending.kind),
|
|
2711
|
+
decision,
|
|
2712
|
+
...reason ? { reason } : {}
|
|
2713
|
+
}
|
|
2323
2714
|
});
|
|
2324
2715
|
}
|
|
2325
2716
|
async respondToUserInput(requestId, answers) {
|
|
2326
2717
|
const pending = this.pendingUserInputs.get(requestId);
|
|
2327
2718
|
if (!pending) throw new Error(`unknown user-input ${requestId}`);
|
|
2328
2719
|
const mapped = toCodexUserInputAnswers(answers);
|
|
2329
|
-
|
|
2720
|
+
if (mapped === void 0) {
|
|
2721
|
+
throw new Error(`invalid user input answers for request ${requestId}`);
|
|
2722
|
+
}
|
|
2723
|
+
pending.settle({ answers: mapped });
|
|
2330
2724
|
this.emit({
|
|
2331
2725
|
eventId: `codex-ui-${requestId}`,
|
|
2332
2726
|
provider: CODEX_DRIVER_KIND,
|
|
@@ -2334,7 +2728,8 @@ var CodexSessionRuntime = class {
|
|
|
2334
2728
|
createdAt: nowIso(),
|
|
2335
2729
|
type: "user-input.resolved",
|
|
2336
2730
|
requestId,
|
|
2337
|
-
|
|
2731
|
+
// 契约的 answers 是 questionId → 答案 的 record,不是数组。
|
|
2732
|
+
payload: { answers: toCanonicalUserInputAnswers(mapped) }
|
|
2338
2733
|
});
|
|
2339
2734
|
}
|
|
2340
2735
|
async withActiveWriterRecovery(run, allowUnsubscribe) {
|
|
@@ -2368,7 +2763,7 @@ var CodexSessionRuntime = class {
|
|
|
2368
2763
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
2369
2764
|
}
|
|
2370
2765
|
threadConfigParams() {
|
|
2371
|
-
const config =
|
|
2766
|
+
const config = this.#threadConfig();
|
|
2372
2767
|
return {
|
|
2373
2768
|
approvalPolicy: config.approvalPolicy,
|
|
2374
2769
|
sandbox: config.sandbox,
|
|
@@ -2419,7 +2814,7 @@ var CodexSessionRuntime = class {
|
|
|
2419
2814
|
threadId: this.threadId,
|
|
2420
2815
|
createdAt: nowIso(),
|
|
2421
2816
|
type: "session.started",
|
|
2422
|
-
payload: {
|
|
2817
|
+
payload: { resume: this.session.resumeCursor }
|
|
2423
2818
|
});
|
|
2424
2819
|
return this.session;
|
|
2425
2820
|
}
|
|
@@ -2451,8 +2846,8 @@ var CodexSessionRuntime = class {
|
|
|
2451
2846
|
if (hasConfiguredMcpServer(this.appServerArgs)) {
|
|
2452
2847
|
await this.rpc.request("mcpServer/reload", {}, 0).catch(() => void 0);
|
|
2453
2848
|
}
|
|
2454
|
-
const sandboxPolicy =
|
|
2455
|
-
const config =
|
|
2849
|
+
const sandboxPolicy = this.#turnSandboxPolicy();
|
|
2850
|
+
const config = this.#threadConfig();
|
|
2456
2851
|
const developerInstructions = this.developerInstructions(input);
|
|
2457
2852
|
const requestedModel = modelIdFromSelection(input.modelSelection) ?? this.model;
|
|
2458
2853
|
const params = {
|
|
@@ -2464,15 +2859,22 @@ var CodexSessionRuntime = class {
|
|
|
2464
2859
|
...requestedModel ? { model: requestedModel } : {},
|
|
2465
2860
|
...developerInstructions ? { developerInstructions } : {}
|
|
2466
2861
|
};
|
|
2467
|
-
|
|
2468
|
-
|
|
2862
|
+
const interactionMode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
|
|
2863
|
+
if (interactionMode !== void 0) {
|
|
2864
|
+
params["collaborationMode"] = codexCollaborationMode({
|
|
2865
|
+
interactionMode,
|
|
2866
|
+
model: requestedModel ?? DEFAULT_MODEL,
|
|
2867
|
+
reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0
|
|
2868
|
+
});
|
|
2469
2869
|
}
|
|
2470
2870
|
const isSteer = input.expectedTurnId !== void 0;
|
|
2471
2871
|
const result = isSteer ? await this.rpc.request(
|
|
2472
2872
|
"turn/steer",
|
|
2473
2873
|
{
|
|
2474
2874
|
...params,
|
|
2475
|
-
turnId
|
|
2875
|
+
// 这一位的名字是 `expectedTurnId`,不是 `turnId`:0.155.1 少了它整条拒收
|
|
2876
|
+
// (`Invalid request: missing field \`expectedTurnId\``),插队的那句话发不出去。
|
|
2877
|
+
expectedTurnId: input.expectedTurnId
|
|
2476
2878
|
},
|
|
2477
2879
|
0
|
|
2478
2880
|
) : await this.withActiveWriterRecovery(() => this.rpc.request("turn/start", params, 0), false);
|
|
@@ -2569,6 +2971,7 @@ var CodexSessionRuntime = class {
|
|
|
2569
2971
|
async close() {
|
|
2570
2972
|
if (this.closeCompleted) return;
|
|
2571
2973
|
this.closed = true;
|
|
2974
|
+
this.requestKinds.clear();
|
|
2572
2975
|
this.failPendingNativeWrites("session closed before write confirmation");
|
|
2573
2976
|
this.settlePendingApprovals("cancel");
|
|
2574
2977
|
this.settlePendingUserInputs({});
|
|
@@ -2792,6 +3195,198 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
|
|
|
2792
3195
|
return model;
|
|
2793
3196
|
}
|
|
2794
3197
|
|
|
3198
|
+
// src/mcpOAuth.ts
|
|
3199
|
+
async function startCodexMcpOAuth(input) {
|
|
3200
|
+
if (input.recipe.binaryPath.length === 0) {
|
|
3201
|
+
throw new Error("Codex program path is not configured");
|
|
3202
|
+
}
|
|
3203
|
+
const mcp = codexMcpLaunchConfig([input.server]);
|
|
3204
|
+
const env = buildCodexSpawnEnv({
|
|
3205
|
+
recipe: input.recipe,
|
|
3206
|
+
layout: input.layout,
|
|
3207
|
+
launchEnv: { ...input.processEnv, ...mcp.environment },
|
|
3208
|
+
crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
|
|
3209
|
+
});
|
|
3210
|
+
const handle = await input.host.process.spawn({
|
|
3211
|
+
program: input.recipe.binaryPath,
|
|
3212
|
+
args: [...tokenizeLaunchArgs(input.recipe.launchArgs), ...mcp.args],
|
|
3213
|
+
cwd: input.cwd,
|
|
3214
|
+
owner: { bindingId: input.host.bindingId },
|
|
3215
|
+
env
|
|
3216
|
+
});
|
|
3217
|
+
let settle;
|
|
3218
|
+
const completed = new Promise((resolve2) => {
|
|
3219
|
+
settle = resolve2;
|
|
3220
|
+
});
|
|
3221
|
+
const rpc = await createCodexRpcClient(handle, {
|
|
3222
|
+
onNotification: (notification) => {
|
|
3223
|
+
if (notification.method !== "mcpServer/oauthLogin/completed") return;
|
|
3224
|
+
const params = isRecord3(notification.params) ? notification.params : {};
|
|
3225
|
+
if (asString(params["name"]) !== input.server.name) return;
|
|
3226
|
+
const error = asString(params["error"]);
|
|
3227
|
+
settle?.({
|
|
3228
|
+
success: params["success"] === true,
|
|
3229
|
+
...error === null || error === void 0 ? {} : { error }
|
|
3230
|
+
});
|
|
3231
|
+
},
|
|
3232
|
+
onServerRequest: () => void 0,
|
|
3233
|
+
onStderrLine: () => void 0
|
|
3234
|
+
});
|
|
3235
|
+
const close = async () => {
|
|
3236
|
+
settle?.({ success: false, error: "cancelled" });
|
|
3237
|
+
await rpc.close().catch(() => void 0);
|
|
3238
|
+
};
|
|
3239
|
+
try {
|
|
3240
|
+
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
3241
|
+
await rpc.notify("initialized");
|
|
3242
|
+
const response = await rpc.request("mcpServer/oauth/login", {
|
|
3243
|
+
name: input.server.name,
|
|
3244
|
+
...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
|
|
3245
|
+
});
|
|
3246
|
+
const authorizationUrl = isRecord3(response) ? asString(response["authorizationUrl"]) : void 0;
|
|
3247
|
+
if (!authorizationUrl) {
|
|
3248
|
+
await close();
|
|
3249
|
+
throw new Error(`Codex did not return an authorization URL for '${input.server.name}'.`);
|
|
3250
|
+
}
|
|
3251
|
+
return { authorizationUrl, handle, completed, close };
|
|
3252
|
+
} catch (error) {
|
|
3253
|
+
await close();
|
|
3254
|
+
throw error;
|
|
3255
|
+
}
|
|
3256
|
+
}
|
|
3257
|
+
|
|
3258
|
+
// src/models.ts
|
|
3259
|
+
var MODEL_LIST_MAX_PAGES = 20;
|
|
3260
|
+
var MODEL_LIST_TIMEOUT_MS = 15e3;
|
|
3261
|
+
var REASONING_EFFORT_LABELS = {
|
|
3262
|
+
none: "None",
|
|
3263
|
+
minimal: "Minimal",
|
|
3264
|
+
low: "Low",
|
|
3265
|
+
medium: "Medium",
|
|
3266
|
+
high: "High",
|
|
3267
|
+
xhigh: "Extra High",
|
|
3268
|
+
max: "Max",
|
|
3269
|
+
ultra: "Ultra"
|
|
3270
|
+
};
|
|
3271
|
+
var DEFAULT_SERVICE_TIER_ID = "default";
|
|
3272
|
+
var reasoningEffortLabel = (effort) => REASONING_EFFORT_LABELS[effort] ?? effort;
|
|
3273
|
+
function codexModelDisplayName(displayName) {
|
|
3274
|
+
return displayName.replace(/^gpt/i, "GPT").replace(/-([a-z])/g, (_, c) => `-${c.toUpperCase()}`);
|
|
3275
|
+
}
|
|
3276
|
+
function reasoningDescriptor(row) {
|
|
3277
|
+
const supported = row["supportedReasoningEfforts"];
|
|
3278
|
+
if (!Array.isArray(supported) || supported.length === 0) return null;
|
|
3279
|
+
const defaultEffort = asString(row["defaultReasoningEffort"]);
|
|
3280
|
+
const options = supported.flatMap((entry) => {
|
|
3281
|
+
if (!isRecord3(entry)) return [];
|
|
3282
|
+
const id = asString(entry["reasoningEffort"]);
|
|
3283
|
+
if (!id) return [];
|
|
3284
|
+
const description = asString(entry["description"]);
|
|
3285
|
+
return [
|
|
3286
|
+
{
|
|
3287
|
+
id,
|
|
3288
|
+
label: reasoningEffortLabel(id),
|
|
3289
|
+
...description ? { description } : {},
|
|
3290
|
+
...id === defaultEffort ? { isDefault: true } : {}
|
|
3291
|
+
}
|
|
3292
|
+
];
|
|
3293
|
+
});
|
|
3294
|
+
if (options.length === 0) return null;
|
|
3295
|
+
return {
|
|
3296
|
+
id: "reasoningEffort",
|
|
3297
|
+
label: "Reasoning",
|
|
3298
|
+
type: "select",
|
|
3299
|
+
options,
|
|
3300
|
+
...defaultEffort ? { currentValue: defaultEffort } : {}
|
|
3301
|
+
};
|
|
3302
|
+
}
|
|
3303
|
+
function serviceTierDescriptor(row) {
|
|
3304
|
+
const declared = Array.isArray(row["serviceTiers"]) ? row["serviceTiers"] : [];
|
|
3305
|
+
const tiers = declared.length > 0 ? declared.flatMap((entry) => {
|
|
3306
|
+
if (!isRecord3(entry)) return [];
|
|
3307
|
+
const id = asString(entry["id"]);
|
|
3308
|
+
if (!id) return [];
|
|
3309
|
+
const name = asString(entry["name"]);
|
|
3310
|
+
const description = asString(entry["description"]);
|
|
3311
|
+
return [{ id, name: name ?? id, ...description ? { description } : {} }];
|
|
3312
|
+
}) : (Array.isArray(row["additionalSpeedTiers"]) ? row["additionalSpeedTiers"] : []).flatMap(
|
|
3313
|
+
(entry) => {
|
|
3314
|
+
const id = typeof entry === "string" ? entry : null;
|
|
3315
|
+
return id === null ? [] : [{ id, name: id === "fast" ? "Fast" : id }];
|
|
3316
|
+
}
|
|
3317
|
+
);
|
|
3318
|
+
if (tiers.length === 0) return null;
|
|
3319
|
+
const declaredDefault = asString(row["defaultServiceTier"]);
|
|
3320
|
+
const defaultTier = declaredDefault && tiers.some((tier) => tier.id === declaredDefault) ? declaredDefault : DEFAULT_SERVICE_TIER_ID;
|
|
3321
|
+
return {
|
|
3322
|
+
id: "serviceTier",
|
|
3323
|
+
label: "Service Tier",
|
|
3324
|
+
type: "select",
|
|
3325
|
+
options: [
|
|
3326
|
+
{
|
|
3327
|
+
id: DEFAULT_SERVICE_TIER_ID,
|
|
3328
|
+
label: "Standard",
|
|
3329
|
+
...defaultTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}
|
|
3330
|
+
},
|
|
3331
|
+
...tiers.map((tier) => ({
|
|
3332
|
+
id: tier.id,
|
|
3333
|
+
label: tier.name,
|
|
3334
|
+
...tier.description ? { description: tier.description } : {},
|
|
3335
|
+
...defaultTier === tier.id ? { isDefault: true } : {}
|
|
3336
|
+
}))
|
|
3337
|
+
],
|
|
3338
|
+
currentValue: defaultTier
|
|
3339
|
+
};
|
|
3340
|
+
}
|
|
3341
|
+
function isSupersededModel(row) {
|
|
3342
|
+
if (isRecord3(row["upgradeInfo"])) return true;
|
|
3343
|
+
return (asString(row["upgrade"]) ?? "").length > 0;
|
|
3344
|
+
}
|
|
3345
|
+
function codexProbeModel(row) {
|
|
3346
|
+
if (!isRecord3(row)) return null;
|
|
3347
|
+
const slug = asString(row["model"]);
|
|
3348
|
+
if (!slug) return null;
|
|
3349
|
+
const displayName = asString(row["displayName"]);
|
|
3350
|
+
const optionDescriptors = [reasoningDescriptor(row), serviceTierDescriptor(row)].filter(
|
|
3351
|
+
(descriptor) => descriptor !== null
|
|
3352
|
+
);
|
|
3353
|
+
return {
|
|
3354
|
+
slug,
|
|
3355
|
+
...displayName ? { name: codexModelDisplayName(displayName) } : {},
|
|
3356
|
+
...row["isDefault"] === true ? { isDefault: true } : {},
|
|
3357
|
+
...isSupersededModel(row) ? { isLegacy: true } : {},
|
|
3358
|
+
...row["hidden"] === true ? { hidden: true } : {},
|
|
3359
|
+
// 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
|
|
3360
|
+
// 但给对象保住「这只手确实答过这一问」这层意思。
|
|
3361
|
+
capabilities: { optionDescriptors }
|
|
3362
|
+
};
|
|
3363
|
+
}
|
|
3364
|
+
async function listCodexModels(rpc) {
|
|
3365
|
+
const models = [];
|
|
3366
|
+
let cursor;
|
|
3367
|
+
for (let page = 0; page < MODEL_LIST_MAX_PAGES; page++) {
|
|
3368
|
+
let response;
|
|
3369
|
+
try {
|
|
3370
|
+
response = await rpc.request(
|
|
3371
|
+
"model/list",
|
|
3372
|
+
cursor === void 0 ? {} : { cursor },
|
|
3373
|
+
MODEL_LIST_TIMEOUT_MS
|
|
3374
|
+
);
|
|
3375
|
+
} catch {
|
|
3376
|
+
return void 0;
|
|
3377
|
+
}
|
|
3378
|
+
if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
|
|
3379
|
+
for (const row of response["data"]) {
|
|
3380
|
+
const model = codexProbeModel(row);
|
|
3381
|
+
if (model !== null) models.push(model);
|
|
3382
|
+
}
|
|
3383
|
+
const next = asString(response["nextCursor"]);
|
|
3384
|
+
if (!next || next === cursor) return models;
|
|
3385
|
+
cursor = next;
|
|
3386
|
+
}
|
|
3387
|
+
return models;
|
|
3388
|
+
}
|
|
3389
|
+
|
|
2795
3390
|
// src/signIn.ts
|
|
2796
3391
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
2797
3392
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -2947,22 +3542,26 @@ async function verifyCodexSignIn(input) {
|
|
|
2947
3542
|
installed: true,
|
|
2948
3543
|
version: null,
|
|
2949
3544
|
readiness: "unchecked",
|
|
2950
|
-
auth: { status: "unauthenticated" }
|
|
3545
|
+
auth: { status: "unauthenticated" },
|
|
3546
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2951
3547
|
};
|
|
2952
3548
|
}
|
|
2953
|
-
|
|
3549
|
+
const models = await listCodexModels(rpc);
|
|
2954
3550
|
return {
|
|
2955
3551
|
installed: true,
|
|
2956
3552
|
version: null,
|
|
2957
3553
|
readiness: "ready",
|
|
2958
|
-
auth
|
|
3554
|
+
auth,
|
|
3555
|
+
...models === void 0 ? {} : { models },
|
|
3556
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2959
3557
|
};
|
|
2960
3558
|
} catch {
|
|
2961
3559
|
return {
|
|
2962
3560
|
installed: true,
|
|
2963
3561
|
version: null,
|
|
2964
3562
|
readiness: "unchecked",
|
|
2965
|
-
auth: { status: "unknown" }
|
|
3563
|
+
auth: { status: "unknown" },
|
|
3564
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2966
3565
|
};
|
|
2967
3566
|
} finally {
|
|
2968
3567
|
await rpc.close().catch(() => void 0);
|
|
@@ -2973,6 +3572,10 @@ async function verifyCodexSignIn(input) {
|
|
|
2973
3572
|
import * as NodeFSP2 from "node:fs/promises";
|
|
2974
3573
|
import * as NodeOS3 from "node:os";
|
|
2975
3574
|
import * as NodePath5 from "node:path";
|
|
3575
|
+
import {
|
|
3576
|
+
mergeLaunchEnv as mergeLaunchEnv2,
|
|
3577
|
+
brainSlotEnvKeys as brainSlotEnvKeys2
|
|
3578
|
+
} from "@ycodium-ai/plugin-api/executor";
|
|
2976
3579
|
var CODEX_TIMEOUT_MS = 18e4;
|
|
2977
3580
|
var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
|
|
2978
3581
|
function limitSection(value, maxChars) {
|
|
@@ -3145,14 +3748,12 @@ function createCodexTextGeneration(input) {
|
|
|
3145
3748
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
3146
3749
|
const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
|
|
3147
3750
|
const model = asString(launchRecord.requestedModelId) ?? modelIdFromSelection(modelSelection) ?? "gpt-5.6-luna";
|
|
3148
|
-
const env = {
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
env[CODEX_INSTANCE_API_KEY_ENV] = input.recipe.environment[CODEX_INSTANCE_API_KEY_ENV];
|
|
3155
|
-
}
|
|
3751
|
+
const env = mergeLaunchEnv2({
|
|
3752
|
+
brainSlots: brainSlotEnvKeys2(CODEX_CARD),
|
|
3753
|
+
accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
|
|
3754
|
+
instanceTable: input.recipe.environment,
|
|
3755
|
+
launch: processEnv
|
|
3756
|
+
});
|
|
3156
3757
|
const args = [
|
|
3157
3758
|
"exec",
|
|
3158
3759
|
...codexExecLaunchArgs(launchArgs),
|
|
@@ -3469,8 +4070,11 @@ function accountToAuth2(account) {
|
|
|
3469
4070
|
return { status: "unknown" };
|
|
3470
4071
|
}
|
|
3471
4072
|
}
|
|
3472
|
-
function layoutFor(recipe, launchHome) {
|
|
4073
|
+
function layoutFor(recipe, launchHome, nativeSubscription = false) {
|
|
3473
4074
|
const environmentHome = recipe.environment["CODEX_HOME"];
|
|
4075
|
+
if (nativeSubscription && launchHome?.trim()) {
|
|
4076
|
+
return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
|
|
4077
|
+
}
|
|
3474
4078
|
return resolveCodexHomeLayout({
|
|
3475
4079
|
homePath: launchHome?.trim() || recipe.homePath,
|
|
3476
4080
|
shadowHomePath: recipe.shadowHomePath,
|
|
@@ -3490,6 +4094,38 @@ var createCodexInstance = (config, host) => {
|
|
|
3490
4094
|
return booked;
|
|
3491
4095
|
};
|
|
3492
4096
|
const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
|
|
4097
|
+
const probeModels = async () => {
|
|
4098
|
+
const layout = layoutFor(recipe);
|
|
4099
|
+
const env = buildCodexSpawnEnv({
|
|
4100
|
+
recipe,
|
|
4101
|
+
layout,
|
|
4102
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
4103
|
+
});
|
|
4104
|
+
let handle;
|
|
4105
|
+
let rpc;
|
|
4106
|
+
try {
|
|
4107
|
+
handle = await host.process.spawn({
|
|
4108
|
+
program: recipe.binaryPath,
|
|
4109
|
+
args: [...codexSpawnArgs(recipe, layout)],
|
|
4110
|
+
owner: { bindingId: host.bindingId },
|
|
4111
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
4112
|
+
});
|
|
4113
|
+
trackHandle(handle, `${recipe.binaryPath} (probe)`);
|
|
4114
|
+
rpc = await createCodexRpcClient(handle, {
|
|
4115
|
+
onNotification: () => void 0,
|
|
4116
|
+
onServerRequest: () => void 0,
|
|
4117
|
+
onStderrLine: () => void 0
|
|
4118
|
+
});
|
|
4119
|
+
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
4120
|
+
await rpc.notify("initialized");
|
|
4121
|
+
return await listCodexModels(rpc);
|
|
4122
|
+
} catch {
|
|
4123
|
+
return void 0;
|
|
4124
|
+
} finally {
|
|
4125
|
+
await rpc?.close().catch(() => void 0);
|
|
4126
|
+
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
4127
|
+
}
|
|
4128
|
+
};
|
|
3493
4129
|
const trackHandle = (handle, command, threadId) => {
|
|
3494
4130
|
live.set(handle.pid, { command, ...threadId === void 0 ? {} : { threadId } });
|
|
3495
4131
|
void handle.exited.finally(() => {
|
|
@@ -3517,14 +4153,20 @@ var createCodexInstance = (config, host) => {
|
|
|
3517
4153
|
version: null,
|
|
3518
4154
|
readiness: "error",
|
|
3519
4155
|
auth: probeAuth(),
|
|
3520
|
-
detail: connectionError
|
|
4156
|
+
detail: connectionError,
|
|
4157
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
3521
4158
|
};
|
|
3522
4159
|
}
|
|
4160
|
+
const models = await probeModels();
|
|
3523
4161
|
return {
|
|
3524
4162
|
installed: true,
|
|
3525
4163
|
version: null,
|
|
3526
|
-
readiness: "unchecked",
|
|
3527
|
-
auth: probeAuth()
|
|
4164
|
+
readiness: models === void 0 ? "unchecked" : "ready",
|
|
4165
|
+
auth: probeAuth(),
|
|
4166
|
+
...models === void 0 ? {} : { models },
|
|
4167
|
+
// 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
|
|
4168
|
+
// 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
|
|
4169
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
3528
4170
|
};
|
|
3529
4171
|
},
|
|
3530
4172
|
startSession: async (input, signal) => {
|
|
@@ -3540,15 +4182,22 @@ var createCodexInstance = (config, host) => {
|
|
|
3540
4182
|
await stopBooked(existing);
|
|
3541
4183
|
threads.delete(input.threadId);
|
|
3542
4184
|
}
|
|
3543
|
-
const
|
|
4185
|
+
const nativeSubscription = input.launch.nativeSubscription === true;
|
|
4186
|
+
const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
|
|
3544
4187
|
await materializeCodexShadowHome(layout, host.log);
|
|
4188
|
+
const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
|
|
3545
4189
|
const env = buildCodexSpawnEnv({
|
|
3546
4190
|
recipe,
|
|
3547
4191
|
layout,
|
|
3548
|
-
|
|
3549
|
-
|
|
4192
|
+
// MCP 那几个变量是宿主挑好的 server 带来的,与绑定同一层:实例表盖不得。
|
|
4193
|
+
launchEnv: { ...input.launch.processEnv, ...mcp.environment },
|
|
4194
|
+
crushApiKeys: nativeSubscription || shouldCrushApiKeys(recipe.authMode)
|
|
3550
4195
|
});
|
|
3551
|
-
const args = [
|
|
4196
|
+
const args = [
|
|
4197
|
+
...codexSpawnArgs(recipe, layout, nativeSubscription),
|
|
4198
|
+
...codexOverrideArgs(input.launch.launchArgOverrides),
|
|
4199
|
+
...mcp.args
|
|
4200
|
+
];
|
|
3552
4201
|
const handle = await host.process.spawn({
|
|
3553
4202
|
program: recipe.binaryPath,
|
|
3554
4203
|
args,
|
|
@@ -3677,7 +4326,6 @@ var createCodexInstance = (config, host) => {
|
|
|
3677
4326
|
const env = buildCodexSpawnEnv({
|
|
3678
4327
|
recipe,
|
|
3679
4328
|
layout,
|
|
3680
|
-
processEnv: {},
|
|
3681
4329
|
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
3682
4330
|
});
|
|
3683
4331
|
const args = [...codexSpawnArgs(recipe, layout)];
|
|
@@ -3715,7 +4363,6 @@ var createCodexInstance = (config, host) => {
|
|
|
3715
4363
|
const env = buildCodexSpawnEnv({
|
|
3716
4364
|
recipe,
|
|
3717
4365
|
layout,
|
|
3718
|
-
processEnv: {},
|
|
3719
4366
|
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
3720
4367
|
});
|
|
3721
4368
|
const args = [...codexSpawnArgs(recipe, layout)];
|
|
@@ -3776,6 +4423,25 @@ var createCodexInstance = (config, host) => {
|
|
|
3776
4423
|
processEnv: process.env
|
|
3777
4424
|
})
|
|
3778
4425
|
},
|
|
4426
|
+
mcpOAuth: {
|
|
4427
|
+
start: async (input) => {
|
|
4428
|
+
const started = await startCodexMcpOAuth({
|
|
4429
|
+
host,
|
|
4430
|
+
recipe,
|
|
4431
|
+
layout: layoutFor(recipe),
|
|
4432
|
+
processEnv: process.env,
|
|
4433
|
+
server: input.server,
|
|
4434
|
+
cwd: input.cwd,
|
|
4435
|
+
...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
|
|
4436
|
+
});
|
|
4437
|
+
trackHandle(started.handle, `${recipe.binaryPath} app-server (mcp oauth)`);
|
|
4438
|
+
return {
|
|
4439
|
+
authorizationUrl: started.authorizationUrl,
|
|
4440
|
+
completed: started.completed,
|
|
4441
|
+
close: started.close
|
|
4442
|
+
};
|
|
4443
|
+
}
|
|
4444
|
+
},
|
|
3779
4445
|
listLiveProcesses: async () => [...live.entries()].map(([pid, entry]) => ({
|
|
3780
4446
|
pid,
|
|
3781
4447
|
command: entry.command,
|