@ycodium-ai/agent-codex 0.2.2106 → 0.2.2130
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 +1040 -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",
|
|
@@ -465,6 +500,13 @@ function resolveCodexHomeLayout(input) {
|
|
|
465
500
|
continuationKey: `codex:home:${sharedHomePath}`
|
|
466
501
|
};
|
|
467
502
|
}
|
|
503
|
+
function codexContinuationKey(input) {
|
|
504
|
+
return resolveCodexHomeLayout({
|
|
505
|
+
homePath: input.homePath,
|
|
506
|
+
shadowHomePath: "",
|
|
507
|
+
environmentHome: input.instanceEnvironment["CODEX_HOME"] ?? input.inheritedEnvironment["CODEX_HOME"]
|
|
508
|
+
}).continuationKey;
|
|
509
|
+
}
|
|
468
510
|
async function readLinkState(linkPath) {
|
|
469
511
|
try {
|
|
470
512
|
const target = await NodeFSP.readlink(linkPath);
|
|
@@ -512,6 +554,16 @@ async function ensureSymlink(input) {
|
|
|
512
554
|
await createEntryLink(target, link);
|
|
513
555
|
}
|
|
514
556
|
}
|
|
557
|
+
async function makeDirectory(directoryPath) {
|
|
558
|
+
try {
|
|
559
|
+
await NodeFSP.mkdir(directoryPath, { recursive: true });
|
|
560
|
+
} catch (error) {
|
|
561
|
+
throw new Error(
|
|
562
|
+
`Codex shadow home filesystem operation 'makeDirectory' failed for '${directoryPath}': ${String(error)}`,
|
|
563
|
+
{ cause: error }
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
515
567
|
async function materializeCodexShadowHome(layout, log) {
|
|
516
568
|
if (layout.mode !== "authOverlay") return;
|
|
517
569
|
const effectiveHomePath = layout.effectiveHomePath;
|
|
@@ -521,11 +573,11 @@ async function materializeCodexShadowHome(layout, log) {
|
|
|
521
573
|
`Codex shadow home path '${effectiveHomePath}' must be different from the shared home path '${layout.sharedHomePath}'.`
|
|
522
574
|
);
|
|
523
575
|
}
|
|
524
|
-
await
|
|
525
|
-
await
|
|
576
|
+
await makeDirectory(layout.sharedHomePath);
|
|
577
|
+
await makeDirectory(effectiveHomePath);
|
|
526
578
|
await Promise.all(
|
|
527
579
|
KNOWN_SHARED_DIRECTORIES.map(
|
|
528
|
-
(directory) =>
|
|
580
|
+
(directory) => makeDirectory(NodePath3.join(layout.sharedHomePath, directory))
|
|
529
581
|
)
|
|
530
582
|
);
|
|
531
583
|
let sharedEntryNames = [];
|
|
@@ -584,6 +636,73 @@ var ACTIVE_WRITER_ATTEMPTS = 5;
|
|
|
584
636
|
var ACTIVE_WRITER_DELAY_MS = 200;
|
|
585
637
|
var DEFAULT_MODEL = "gpt-5.6-sol";
|
|
586
638
|
var DEFAULT_TEXT_GENERATION_REASONING_EFFORT = "low";
|
|
639
|
+
var CODEX_NATIVE_MODES = [
|
|
640
|
+
{
|
|
641
|
+
id: "read-only",
|
|
642
|
+
name: "Read only",
|
|
643
|
+
description: "Sandboxed to reading. Asks before anything that writes or runs."
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
id: "workspace-write",
|
|
647
|
+
name: "Workspace write",
|
|
648
|
+
description: "Writes inside the workspace. Asks you before stepping outside it."
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
id: "auto-review",
|
|
652
|
+
name: "Auto review",
|
|
653
|
+
description: "Writes inside the workspace. Codex approves its own requests, not you."
|
|
654
|
+
},
|
|
655
|
+
{
|
|
656
|
+
id: "full-access",
|
|
657
|
+
name: "Full access",
|
|
658
|
+
description: "No sandbox, never asks. Every command runs immediately."
|
|
659
|
+
}
|
|
660
|
+
];
|
|
661
|
+
var CODEX_DEFAULT_NATIVE_MODE_ID = "read-only";
|
|
662
|
+
var CODEX_NATIVE_MODE_BY_RUNTIME_MODE = {
|
|
663
|
+
"approval-required": "read-only",
|
|
664
|
+
"auto-accept-edits": "workspace-write",
|
|
665
|
+
auto: "auto-review",
|
|
666
|
+
"full-access": "full-access"
|
|
667
|
+
};
|
|
668
|
+
var CODEX_NATIVE_MODE_REPORT = {
|
|
669
|
+
nativeModes: CODEX_NATIVE_MODES.map((mode) => ({
|
|
670
|
+
id: mode.id,
|
|
671
|
+
name: mode.name,
|
|
672
|
+
description: mode.description
|
|
673
|
+
})),
|
|
674
|
+
currentNativeModeId: CODEX_DEFAULT_NATIVE_MODE_ID,
|
|
675
|
+
nativeModeByRuntimeMode: CODEX_NATIVE_MODE_BY_RUNTIME_MODE
|
|
676
|
+
};
|
|
677
|
+
function isCodexNativeModeId(value) {
|
|
678
|
+
return CODEX_NATIVE_MODES.some((mode) => mode.id === value);
|
|
679
|
+
}
|
|
680
|
+
function codexNativeModeFor(runtimeMode, nativeModeId) {
|
|
681
|
+
return nativeModeId !== void 0 && isCodexNativeModeId(nativeModeId) ? nativeModeId : CODEX_NATIVE_MODE_BY_RUNTIME_MODE[runtimeMode];
|
|
682
|
+
}
|
|
683
|
+
function codexThreadConfigForNativeMode(nativeModeId) {
|
|
684
|
+
switch (nativeModeId) {
|
|
685
|
+
case "workspace-write":
|
|
686
|
+
return runtimeModeToThreadConfig("auto-accept-edits");
|
|
687
|
+
case "auto-review":
|
|
688
|
+
return runtimeModeToThreadConfig("auto");
|
|
689
|
+
case "full-access":
|
|
690
|
+
return runtimeModeToThreadConfig("full-access");
|
|
691
|
+
default:
|
|
692
|
+
return runtimeModeToThreadConfig("approval-required");
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
function codexTurnSandboxPolicyForNativeMode(nativeModeId) {
|
|
696
|
+
switch (nativeModeId) {
|
|
697
|
+
case "workspace-write":
|
|
698
|
+
case "auto-review":
|
|
699
|
+
return runtimeModeToTurnSandboxPolicy("auto-accept-edits");
|
|
700
|
+
case "full-access":
|
|
701
|
+
return runtimeModeToTurnSandboxPolicy("full-access");
|
|
702
|
+
default:
|
|
703
|
+
return runtimeModeToTurnSandboxPolicy("approval-required");
|
|
704
|
+
}
|
|
705
|
+
}
|
|
587
706
|
function runtimeModeToThreadConfig(mode) {
|
|
588
707
|
switch (mode) {
|
|
589
708
|
case "approval-required":
|
|
@@ -617,6 +736,16 @@ function runtimeModeToTurnSandboxPolicy(mode) {
|
|
|
617
736
|
return { type: "dangerFullAccess" };
|
|
618
737
|
}
|
|
619
738
|
}
|
|
739
|
+
function codexCollaborationMode(input) {
|
|
740
|
+
const effort = input.reasoningEffort?.trim();
|
|
741
|
+
return {
|
|
742
|
+
mode: input.interactionMode === "plan" ? "plan" : "default",
|
|
743
|
+
settings: {
|
|
744
|
+
model: input.model,
|
|
745
|
+
...effort ? { reasoning_effort: effort } : {}
|
|
746
|
+
}
|
|
747
|
+
};
|
|
748
|
+
}
|
|
620
749
|
function isRecord3(value) {
|
|
621
750
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
622
751
|
}
|
|
@@ -697,11 +826,19 @@ var CHILD_AGENT_EVENT_METHODS = /* @__PURE__ */ new Set([
|
|
|
697
826
|
]);
|
|
698
827
|
var CHILD_CHATTER_METHODS = /* @__PURE__ */ new Set([
|
|
699
828
|
"item/agentMessage/delta",
|
|
700
|
-
"item/reasoning/
|
|
701
|
-
"item/reasoning/
|
|
829
|
+
"item/reasoning/textDelta",
|
|
830
|
+
"item/reasoning/summaryTextDelta",
|
|
702
831
|
"item/reasoning/summaryPartAdded",
|
|
832
|
+
"item/commandExecution/outputDelta",
|
|
833
|
+
"item/fileChange/outputDelta",
|
|
834
|
+
"item/fileChange/patchUpdated",
|
|
703
835
|
"item/plan/delta",
|
|
836
|
+
"turn/plan/updated",
|
|
837
|
+
"turn/diff/updated",
|
|
704
838
|
"thread/name/updated",
|
|
839
|
+
"thread/settings/updated",
|
|
840
|
+
"rawResponseItem/completed",
|
|
841
|
+
// 子线程自己的生命周期通知不能改父线程状态(旧宿主 runtime 同名单)。
|
|
705
842
|
"thread/archived",
|
|
706
843
|
"thread/unarchived",
|
|
707
844
|
"thread/compacted",
|
|
@@ -1120,95 +1257,6 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
|
|
|
1120
1257
|
<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
1258
|
}
|
|
1122
1259
|
|
|
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
1260
|
// src/events.ts
|
|
1213
1261
|
var CODEX_PROVIDER = "codex";
|
|
1214
1262
|
var eventSeq = 0;
|
|
@@ -1225,28 +1273,27 @@ function nowIso2() {
|
|
|
1225
1273
|
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1226
1274
|
}
|
|
1227
1275
|
function normalizeItemType(value) {
|
|
1228
|
-
if (!value) return "
|
|
1229
|
-
return value.replace(/([a-
|
|
1276
|
+
if (!value) return "item";
|
|
1277
|
+
return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
|
|
1230
1278
|
}
|
|
1231
1279
|
function toCanonicalItemType(raw) {
|
|
1232
1280
|
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";
|
|
1281
|
+
if (type.includes("user")) return "user_message";
|
|
1282
|
+
if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
|
|
1283
|
+
if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
|
|
1237
1284
|
if (type.includes("plan") || type.includes("todo")) return "plan";
|
|
1238
|
-
if (type.includes("command")) return "
|
|
1285
|
+
if (type.includes("command")) return "command_execution";
|
|
1239
1286
|
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 "
|
|
1287
|
+
return "file_change";
|
|
1288
|
+
}
|
|
1289
|
+
if (type.includes("mcp")) return "mcp_tool_call";
|
|
1290
|
+
if (type.includes("dynamic tool")) return "dynamic_tool_call";
|
|
1291
|
+
if (type.includes("collab")) return "collab_agent_tool_call";
|
|
1292
|
+
if (type.includes("web search")) return "web_search";
|
|
1293
|
+
if (type.includes("image")) return "image_view";
|
|
1294
|
+
if (type.includes("review entered")) return "review_entered";
|
|
1295
|
+
if (type.includes("review exited")) return "review_exited";
|
|
1296
|
+
if (type.includes("compact")) return "context_compaction";
|
|
1250
1297
|
if (type.includes("error")) return "error";
|
|
1251
1298
|
return "unknown";
|
|
1252
1299
|
}
|
|
@@ -1255,6 +1302,11 @@ function itemIdOf(params) {
|
|
|
1255
1302
|
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1256
1303
|
return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
|
|
1257
1304
|
}
|
|
1305
|
+
function itemStatusOf(params) {
|
|
1306
|
+
if (!isRecord3(params)) return void 0;
|
|
1307
|
+
const item = isRecord3(params["item"]) ? params["item"] : params;
|
|
1308
|
+
return asString(item["status"]) ?? void 0;
|
|
1309
|
+
}
|
|
1258
1310
|
function turnIdOf(params) {
|
|
1259
1311
|
if (!isRecord3(params)) return void 0;
|
|
1260
1312
|
return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
|
|
@@ -1287,45 +1339,45 @@ function toTurnStatus(status) {
|
|
|
1287
1339
|
return "completed";
|
|
1288
1340
|
}
|
|
1289
1341
|
}
|
|
1290
|
-
function
|
|
1291
|
-
|
|
1292
|
-
if (
|
|
1342
|
+
function threadStateFromStatus(status) {
|
|
1343
|
+
const type = isRecord3(status) ? asString(status["type"]) : asString(status);
|
|
1344
|
+
if (type === "idle") return "idle";
|
|
1345
|
+
if (type === "systemError" || type === "error") return "error";
|
|
1293
1346
|
return "active";
|
|
1294
1347
|
}
|
|
1295
|
-
function
|
|
1296
|
-
|
|
1297
|
-
if (method.includes("reasoning")) return "reasoning";
|
|
1298
|
-
if (method.includes("plan")) return "plan";
|
|
1299
|
-
return void 0;
|
|
1348
|
+
function codexCompactionId(threadId, turnId) {
|
|
1349
|
+
return `codex:${threadId}:${turnId ?? "session"}`;
|
|
1300
1350
|
}
|
|
1301
|
-
function normalizeCodexTokenUsage(
|
|
1302
|
-
if (!isRecord3(
|
|
1303
|
-
const
|
|
1304
|
-
const
|
|
1305
|
-
const
|
|
1306
|
-
const usedTokens =
|
|
1307
|
-
if (usedTokens ===
|
|
1308
|
-
const
|
|
1309
|
-
const
|
|
1351
|
+
function normalizeCodexTokenUsage(tokenUsage) {
|
|
1352
|
+
if (!isRecord3(tokenUsage)) return void 0;
|
|
1353
|
+
const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
|
|
1354
|
+
const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
1355
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1356
|
+
const usedTokens = count(last?.["totalTokens"]);
|
|
1357
|
+
if (usedTokens === void 0 || usedTokens <= 0) return void 0;
|
|
1358
|
+
const totalProcessedTokens = count(total?.["totalTokens"]);
|
|
1359
|
+
const maxTokens = count(tokenUsage["modelContextWindow"]);
|
|
1360
|
+
const inputTokens = count(last?.["inputTokens"]);
|
|
1361
|
+
const cachedInputTokens = count(last?.["cachedInputTokens"]);
|
|
1362
|
+
const outputTokens = count(last?.["outputTokens"]);
|
|
1363
|
+
const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
|
|
1364
|
+
return {
|
|
1310
1365
|
usedTokens,
|
|
1366
|
+
lastUsedTokens: usedTokens,
|
|
1367
|
+
...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
|
|
1368
|
+
...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
|
|
1369
|
+
...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
|
|
1370
|
+
...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
|
|
1371
|
+
...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
|
|
1372
|
+
...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
|
|
1311
1373
|
compactsAutomatically: true
|
|
1312
1374
|
};
|
|
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
1375
|
}
|
|
1324
1376
|
function turnCompletedControlFlow(params) {
|
|
1325
1377
|
const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
|
|
1326
|
-
const
|
|
1378
|
+
const state = toTurnStatus(asString(turn["status"]) ?? void 0);
|
|
1327
1379
|
const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
|
|
1328
|
-
return error ? {
|
|
1380
|
+
return error ? { state, errorMessage: error } : { state };
|
|
1329
1381
|
}
|
|
1330
1382
|
function toApplyPatchFileChanges(params) {
|
|
1331
1383
|
if (!isRecord3(params)) return [];
|
|
@@ -1343,10 +1395,33 @@ function toApplyPatchFileChanges(params) {
|
|
|
1343
1395
|
if (!path) continue;
|
|
1344
1396
|
const kind = (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase();
|
|
1345
1397
|
const movePath = asString(entry["move_path"]) ?? asString(entry["movePath"]);
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1398
|
+
const diff = asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]);
|
|
1399
|
+
const content = asString(entry["content"]);
|
|
1400
|
+
if (kind === "add") {
|
|
1401
|
+
out.push({
|
|
1402
|
+
path,
|
|
1403
|
+
changeKind: "add",
|
|
1404
|
+
...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
|
|
1405
|
+
});
|
|
1406
|
+
} else if (kind === "delete") {
|
|
1407
|
+
out.push({
|
|
1408
|
+
path,
|
|
1409
|
+
changeKind: "delete",
|
|
1410
|
+
...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
|
|
1411
|
+
});
|
|
1412
|
+
} else if (movePath) {
|
|
1413
|
+
out.push({
|
|
1414
|
+
path,
|
|
1415
|
+
changeKind: "rename",
|
|
1416
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1417
|
+
});
|
|
1418
|
+
} else {
|
|
1419
|
+
out.push({
|
|
1420
|
+
path,
|
|
1421
|
+
changeKind: "modify",
|
|
1422
|
+
...diff ? { content: { kind: "unified", diff } } : {}
|
|
1423
|
+
});
|
|
1424
|
+
}
|
|
1350
1425
|
}
|
|
1351
1426
|
return out;
|
|
1352
1427
|
}
|
|
@@ -1359,7 +1434,18 @@ function toUserInputQuestions(params) {
|
|
|
1359
1434
|
const id = asString(question["id"]);
|
|
1360
1435
|
const header = asString(question["header"]);
|
|
1361
1436
|
const text = asString(question["question"]);
|
|
1362
|
-
const
|
|
1437
|
+
const rawOptions = Array.isArray(question["options"]) ? question["options"] : [];
|
|
1438
|
+
const options = [];
|
|
1439
|
+
for (const option of rawOptions) {
|
|
1440
|
+
if (!isRecord3(option)) continue;
|
|
1441
|
+
const label = trimText(option["label"]);
|
|
1442
|
+
if (!label) continue;
|
|
1443
|
+
options.push({
|
|
1444
|
+
...asString(option["id"]) ? { id: asString(option["id"]) } : {},
|
|
1445
|
+
label,
|
|
1446
|
+
description: trimText(option["description"]) ?? ""
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1363
1449
|
if (!id || !header || !text || options.length === 0) return void 0;
|
|
1364
1450
|
mapped.push({
|
|
1365
1451
|
id,
|
|
@@ -1372,15 +1458,32 @@ function toUserInputQuestions(params) {
|
|
|
1372
1458
|
return mapped.length > 0 ? mapped : void 0;
|
|
1373
1459
|
}
|
|
1374
1460
|
function toCodexUserInputAnswers(answers) {
|
|
1375
|
-
if (
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1461
|
+
if (!isRecord3(answers)) return void 0;
|
|
1462
|
+
const out = {};
|
|
1463
|
+
for (const [questionId, value] of Object.entries(answers)) {
|
|
1464
|
+
if (typeof value === "string") {
|
|
1465
|
+
out[questionId] = { answers: [value] };
|
|
1466
|
+
continue;
|
|
1467
|
+
}
|
|
1468
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
1469
|
+
out[questionId] = { answers: [...value] };
|
|
1470
|
+
continue;
|
|
1471
|
+
}
|
|
1472
|
+
if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
|
|
1473
|
+
out[questionId] = { answers: [...value["answers"]] };
|
|
1474
|
+
continue;
|
|
1475
|
+
}
|
|
1476
|
+
return void 0;
|
|
1382
1477
|
}
|
|
1383
|
-
return
|
|
1478
|
+
return out;
|
|
1479
|
+
}
|
|
1480
|
+
function toCanonicalUserInputAnswers(answers) {
|
|
1481
|
+
return Object.fromEntries(
|
|
1482
|
+
Object.entries(answers).map(([questionId, value]) => [
|
|
1483
|
+
questionId,
|
|
1484
|
+
value.answers.length === 1 ? value.answers[0] : [...value.answers]
|
|
1485
|
+
])
|
|
1486
|
+
);
|
|
1384
1487
|
}
|
|
1385
1488
|
function pathLeaf(value) {
|
|
1386
1489
|
if (!value) return void 0;
|
|
@@ -1418,10 +1521,11 @@ function mapCollabAgentEvent(input) {
|
|
|
1418
1521
|
const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
|
|
1419
1522
|
const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
|
|
1420
1523
|
const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
|
|
1421
|
-
const
|
|
1524
|
+
const knownName = nickname ?? pathLeaf(agentPath);
|
|
1525
|
+
const title = knownName ?? agentThreadId;
|
|
1422
1526
|
const linkage = {
|
|
1423
1527
|
role,
|
|
1424
|
-
title,
|
|
1528
|
+
...knownName ? { title: knownName } : {},
|
|
1425
1529
|
...agentPath ? { agentPath } : {},
|
|
1426
1530
|
timelineBypass: true
|
|
1427
1531
|
};
|
|
@@ -1439,14 +1543,17 @@ function mapCollabAgentEvent(input) {
|
|
|
1439
1543
|
];
|
|
1440
1544
|
}
|
|
1441
1545
|
if (method === "collabAgent/progress" || method === "task.progress") {
|
|
1546
|
+
const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
|
|
1442
1547
|
return [
|
|
1443
1548
|
baseEvent({
|
|
1444
1549
|
type: "task.progress",
|
|
1445
1550
|
threadId,
|
|
1551
|
+
// 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
|
|
1446
1552
|
payload: {
|
|
1447
1553
|
taskId: agentThreadId,
|
|
1448
|
-
|
|
1449
|
-
|
|
1554
|
+
description: title,
|
|
1555
|
+
...summary ? { summary } : {},
|
|
1556
|
+
...linkage
|
|
1450
1557
|
},
|
|
1451
1558
|
method,
|
|
1452
1559
|
providerInstanceId: input.providerInstanceId
|
|
@@ -1456,37 +1563,53 @@ function mapCollabAgentEvent(input) {
|
|
|
1456
1563
|
if (method === "collabAgent/completed" || method === "task.completed") {
|
|
1457
1564
|
return [
|
|
1458
1565
|
baseEvent({
|
|
1459
|
-
type: "task.
|
|
1566
|
+
type: "task.updated",
|
|
1460
1567
|
threadId,
|
|
1461
|
-
payload: { taskId: agentThreadId, ...linkage },
|
|
1568
|
+
payload: { taskId: agentThreadId, status: "idle", ...linkage },
|
|
1462
1569
|
method,
|
|
1463
1570
|
providerInstanceId: input.providerInstanceId
|
|
1464
1571
|
})
|
|
1465
1572
|
];
|
|
1466
1573
|
}
|
|
1467
1574
|
if (method === "collabAgent/statusChanged") {
|
|
1575
|
+
const raw = asString(input.params["status"]);
|
|
1576
|
+
const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
|
|
1468
1577
|
return [
|
|
1469
1578
|
baseEvent({
|
|
1470
1579
|
type: "task.updated",
|
|
1471
1580
|
threadId,
|
|
1472
|
-
payload: {
|
|
1473
|
-
taskId: agentThreadId,
|
|
1474
|
-
...linkage,
|
|
1475
|
-
status: asString(input.params["status"]) ?? "running"
|
|
1476
|
-
},
|
|
1581
|
+
payload: { taskId: agentThreadId, status, ...linkage },
|
|
1477
1582
|
method,
|
|
1478
1583
|
providerInstanceId: input.providerInstanceId
|
|
1479
1584
|
})
|
|
1480
1585
|
];
|
|
1481
1586
|
}
|
|
1482
1587
|
if (method === "collabAgent/tokenUsage") {
|
|
1483
|
-
const
|
|
1484
|
-
|
|
1588
|
+
const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
|
|
1589
|
+
const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
|
|
1590
|
+
const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
|
|
1591
|
+
const totalTokens = total ? count(total["totalTokens"]) : void 0;
|
|
1592
|
+
if (total === void 0 || totalTokens === void 0) return [];
|
|
1593
|
+
const typedUsage = { totalTokens };
|
|
1594
|
+
for (const key of [
|
|
1595
|
+
"inputTokens",
|
|
1596
|
+
"cachedInputTokens",
|
|
1597
|
+
"outputTokens",
|
|
1598
|
+
"reasoningOutputTokens"
|
|
1599
|
+
]) {
|
|
1600
|
+
const value = count(total[key]);
|
|
1601
|
+
if (value !== void 0) typedUsage[key] = value;
|
|
1602
|
+
}
|
|
1485
1603
|
return [
|
|
1486
1604
|
baseEvent({
|
|
1487
|
-
type: "
|
|
1488
|
-
threadId
|
|
1489
|
-
payload:
|
|
1605
|
+
type: "task.progress",
|
|
1606
|
+
threadId,
|
|
1607
|
+
payload: {
|
|
1608
|
+
taskId: agentThreadId,
|
|
1609
|
+
description: title,
|
|
1610
|
+
typedUsage,
|
|
1611
|
+
...linkage
|
|
1612
|
+
},
|
|
1490
1613
|
method,
|
|
1491
1614
|
providerInstanceId: input.providerInstanceId
|
|
1492
1615
|
})
|
|
@@ -1581,52 +1704,6 @@ function toMcpElicitationResponse(decision, params) {
|
|
|
1581
1704
|
...persist ? { _meta: { persist } } : {}
|
|
1582
1705
|
};
|
|
1583
1706
|
}
|
|
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
1707
|
function mapNotificationToEvents(input) {
|
|
1631
1708
|
const { method, params, threadId, providerInstanceId } = input;
|
|
1632
1709
|
if (method.startsWith("collabAgent/")) {
|
|
@@ -1678,58 +1755,121 @@ function mapNotificationToEvents(input) {
|
|
|
1678
1755
|
const itemId = itemIdOf(params);
|
|
1679
1756
|
const type = itemTypeOf(params);
|
|
1680
1757
|
switch (method) {
|
|
1681
|
-
case "thread/started":
|
|
1758
|
+
case "thread/started": {
|
|
1759
|
+
const startedThread = isRecord3(params) ? params["thread"] : void 0;
|
|
1760
|
+
const providerThreadId = isRecord3(startedThread) ? trimText(startedThread["id"]) : void 0;
|
|
1682
1761
|
return [
|
|
1683
1762
|
baseEvent({
|
|
1684
1763
|
type: "thread.started",
|
|
1685
1764
|
threadId,
|
|
1686
|
-
payload:
|
|
1765
|
+
payload: providerThreadId ? { providerThreadId } : {},
|
|
1687
1766
|
method,
|
|
1688
1767
|
providerInstanceId
|
|
1689
1768
|
})
|
|
1690
1769
|
];
|
|
1770
|
+
}
|
|
1691
1771
|
case "thread/status/changed": {
|
|
1692
|
-
const status = isRecord3(params) ?
|
|
1772
|
+
const status = isRecord3(params) ? params["status"] : void 0;
|
|
1693
1773
|
return [
|
|
1694
1774
|
baseEvent({
|
|
1695
1775
|
type: "thread.state.changed",
|
|
1696
1776
|
threadId,
|
|
1697
|
-
payload: {
|
|
1777
|
+
payload: {
|
|
1778
|
+
state: threadStateFromStatus(status),
|
|
1779
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1780
|
+
},
|
|
1698
1781
|
method,
|
|
1699
1782
|
providerInstanceId
|
|
1700
1783
|
})
|
|
1701
1784
|
];
|
|
1702
1785
|
}
|
|
1703
|
-
case "thread/
|
|
1786
|
+
case "thread/archived":
|
|
1787
|
+
case "thread/unarchived":
|
|
1788
|
+
return [
|
|
1789
|
+
baseEvent({
|
|
1790
|
+
type: "thread.state.changed",
|
|
1791
|
+
threadId,
|
|
1792
|
+
payload: {
|
|
1793
|
+
state: method === "thread/archived" ? "archived" : "active",
|
|
1794
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1795
|
+
},
|
|
1796
|
+
method,
|
|
1797
|
+
providerInstanceId
|
|
1798
|
+
})
|
|
1799
|
+
];
|
|
1800
|
+
case "thread/closed":
|
|
1801
|
+
return [
|
|
1802
|
+
baseEvent({
|
|
1803
|
+
type: "thread.state.changed",
|
|
1804
|
+
threadId,
|
|
1805
|
+
payload: {
|
|
1806
|
+
state: "closed",
|
|
1807
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1808
|
+
},
|
|
1809
|
+
method,
|
|
1810
|
+
providerInstanceId
|
|
1811
|
+
})
|
|
1812
|
+
];
|
|
1813
|
+
case "thread/compacted":
|
|
1814
|
+
return [
|
|
1815
|
+
baseEvent({
|
|
1816
|
+
type: "thread.state.changed",
|
|
1817
|
+
threadId,
|
|
1818
|
+
payload: {
|
|
1819
|
+
state: "compacted",
|
|
1820
|
+
compaction: {
|
|
1821
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1822
|
+
// Codex 不报触发方,手动压缩也走同一条通知。
|
|
1823
|
+
trigger: "automatic"
|
|
1824
|
+
},
|
|
1825
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1826
|
+
},
|
|
1827
|
+
method,
|
|
1828
|
+
providerInstanceId
|
|
1829
|
+
})
|
|
1830
|
+
];
|
|
1831
|
+
case "thread/name/updated": {
|
|
1832
|
+
const threadName = isRecord3(params) ? trimText(params["threadName"]) ?? trimText(params["name"]) : void 0;
|
|
1833
|
+
const nativeThreadId = isRecord3(params) ? asString(params["threadId"]) : void 0;
|
|
1704
1834
|
return [
|
|
1705
1835
|
baseEvent({
|
|
1706
1836
|
type: "thread.metadata.updated",
|
|
1707
1837
|
threadId,
|
|
1708
|
-
payload: {
|
|
1838
|
+
payload: {
|
|
1839
|
+
...threadName ? { name: threadName } : {},
|
|
1840
|
+
...nativeThreadId || threadName !== void 0 ? {
|
|
1841
|
+
metadata: {
|
|
1842
|
+
...nativeThreadId ? { threadId: nativeThreadId } : {},
|
|
1843
|
+
...threadName !== void 0 ? { threadName } : {}
|
|
1844
|
+
}
|
|
1845
|
+
} : {}
|
|
1846
|
+
},
|
|
1709
1847
|
method,
|
|
1710
1848
|
providerInstanceId
|
|
1711
1849
|
})
|
|
1712
1850
|
];
|
|
1851
|
+
}
|
|
1713
1852
|
case "thread/tokenUsage/updated": {
|
|
1714
|
-
const usage = normalizeCodexTokenUsage(
|
|
1853
|
+
const usage = normalizeCodexTokenUsage(
|
|
1854
|
+
isRecord3(params) ? params["tokenUsage"] ?? params["usage"] : void 0
|
|
1855
|
+
);
|
|
1715
1856
|
if (!usage) return [];
|
|
1716
1857
|
return [
|
|
1717
1858
|
baseEvent({
|
|
1718
1859
|
type: "thread.token-usage.updated",
|
|
1719
1860
|
threadId,
|
|
1720
|
-
payload: usage,
|
|
1861
|
+
payload: { usage },
|
|
1721
1862
|
method,
|
|
1722
1863
|
providerInstanceId
|
|
1723
1864
|
})
|
|
1724
1865
|
];
|
|
1725
1866
|
}
|
|
1726
|
-
case "thread/closed":
|
|
1727
1867
|
case "session/closed":
|
|
1728
1868
|
return [
|
|
1729
1869
|
baseEvent({
|
|
1730
1870
|
type: "session.exited",
|
|
1731
1871
|
threadId,
|
|
1732
|
-
payload: {},
|
|
1872
|
+
payload: { exitKind: "graceful" },
|
|
1733
1873
|
method,
|
|
1734
1874
|
providerInstanceId
|
|
1735
1875
|
})
|
|
@@ -1740,43 +1880,85 @@ function mapNotificationToEvents(input) {
|
|
|
1740
1880
|
type: "turn.started",
|
|
1741
1881
|
threadId,
|
|
1742
1882
|
turnId,
|
|
1743
|
-
payload: {
|
|
1883
|
+
payload: {},
|
|
1744
1884
|
method,
|
|
1745
1885
|
providerInstanceId
|
|
1746
1886
|
})
|
|
1747
1887
|
];
|
|
1748
|
-
case "turn/completed":
|
|
1749
|
-
const flow = turnCompletedControlFlow(params);
|
|
1750
|
-
const typeName = flow.status === "cancelled" || flow.status === "interrupted" ? "turn.aborted" : "turn.completed";
|
|
1888
|
+
case "turn/completed":
|
|
1751
1889
|
return [
|
|
1752
1890
|
baseEvent({
|
|
1753
|
-
type:
|
|
1891
|
+
type: "turn.completed",
|
|
1754
1892
|
threadId,
|
|
1755
1893
|
turnId,
|
|
1756
|
-
payload:
|
|
1894
|
+
payload: turnCompletedControlFlow(params),
|
|
1895
|
+
method,
|
|
1896
|
+
providerInstanceId
|
|
1897
|
+
})
|
|
1898
|
+
];
|
|
1899
|
+
case "turn/aborted":
|
|
1900
|
+
return [
|
|
1901
|
+
baseEvent({
|
|
1902
|
+
type: "turn.aborted",
|
|
1903
|
+
threadId,
|
|
1904
|
+
turnId,
|
|
1905
|
+
payload: {
|
|
1906
|
+
reason: (isRecord3(params) ? trimText(params["reason"]) : void 0) ?? (isRecord3(params) ? trimText(params["message"]) : void 0) ?? "Turn aborted"
|
|
1907
|
+
},
|
|
1757
1908
|
method,
|
|
1758
1909
|
providerInstanceId
|
|
1759
1910
|
})
|
|
1760
1911
|
];
|
|
1761
|
-
}
|
|
1762
1912
|
case "turn/plan":
|
|
1913
|
+
case "turn/plan/updated": {
|
|
1914
|
+
const rawPlan = isRecord3(params) && Array.isArray(params["plan"]) ? params["plan"] : [];
|
|
1915
|
+
const plan = rawPlan.filter(isRecord3).map((step) => ({
|
|
1916
|
+
step: trimText(step["step"]) ?? "step",
|
|
1917
|
+
status: step["status"] === "completed" || step["status"] === "inProgress" ? step["status"] : "pending"
|
|
1918
|
+
}));
|
|
1919
|
+
const explanation = isRecord3(params) ? trimText(params["explanation"]) : void 0;
|
|
1763
1920
|
return [
|
|
1764
1921
|
baseEvent({
|
|
1765
1922
|
type: "turn.plan.updated",
|
|
1766
1923
|
threadId,
|
|
1767
1924
|
turnId,
|
|
1768
|
-
payload:
|
|
1925
|
+
payload: {
|
|
1926
|
+
...explanation ? { explanation } : {},
|
|
1927
|
+
plan
|
|
1928
|
+
},
|
|
1929
|
+
method,
|
|
1930
|
+
providerInstanceId
|
|
1931
|
+
})
|
|
1932
|
+
];
|
|
1933
|
+
}
|
|
1934
|
+
case "turn/diff/updated": {
|
|
1935
|
+
const unifiedDiff = isRecord3(params) ? asString(params["diff"]) : void 0;
|
|
1936
|
+
if (unifiedDiff === void 0) return [];
|
|
1937
|
+
return [
|
|
1938
|
+
baseEvent({
|
|
1939
|
+
type: "turn.diff.updated",
|
|
1940
|
+
threadId,
|
|
1941
|
+
turnId,
|
|
1942
|
+
payload: { unifiedDiff },
|
|
1769
1943
|
method,
|
|
1770
1944
|
providerInstanceId
|
|
1771
1945
|
})
|
|
1772
1946
|
];
|
|
1947
|
+
}
|
|
1773
1948
|
case "item/started": {
|
|
1774
1949
|
if (type && normalizeItemType(type).includes("compact")) {
|
|
1775
1950
|
return [
|
|
1776
1951
|
baseEvent({
|
|
1777
1952
|
type: "thread.state.changed",
|
|
1778
1953
|
threadId,
|
|
1779
|
-
payload: {
|
|
1954
|
+
payload: {
|
|
1955
|
+
state: "compacting",
|
|
1956
|
+
compaction: {
|
|
1957
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1958
|
+
trigger: "automatic"
|
|
1959
|
+
},
|
|
1960
|
+
...params !== void 0 ? { detail: params } : {}
|
|
1961
|
+
},
|
|
1780
1962
|
turnId,
|
|
1781
1963
|
itemId,
|
|
1782
1964
|
method,
|
|
@@ -1784,6 +1966,8 @@ function mapNotificationToEvents(input) {
|
|
|
1784
1966
|
})
|
|
1785
1967
|
];
|
|
1786
1968
|
}
|
|
1969
|
+
const canonical = toCanonicalItemType(type);
|
|
1970
|
+
if (canonical === "unknown") return [];
|
|
1787
1971
|
return [
|
|
1788
1972
|
baseEvent({
|
|
1789
1973
|
type: "item.started",
|
|
@@ -1791,9 +1975,11 @@ function mapNotificationToEvents(input) {
|
|
|
1791
1975
|
turnId,
|
|
1792
1976
|
itemId,
|
|
1793
1977
|
payload: {
|
|
1794
|
-
itemType:
|
|
1795
|
-
|
|
1796
|
-
|
|
1978
|
+
itemType: canonical,
|
|
1979
|
+
status: "inProgress",
|
|
1980
|
+
...itemTitle(params) ? { title: itemTitle(params) } : {},
|
|
1981
|
+
...itemDetail(params) ? { detail: itemDetail(params) } : {},
|
|
1982
|
+
...params !== void 0 ? { data: params } : {}
|
|
1797
1983
|
},
|
|
1798
1984
|
method,
|
|
1799
1985
|
providerInstanceId
|
|
@@ -1806,7 +1992,14 @@ function mapNotificationToEvents(input) {
|
|
|
1806
1992
|
baseEvent({
|
|
1807
1993
|
type: "thread.state.changed",
|
|
1808
1994
|
threadId,
|
|
1809
|
-
payload: {
|
|
1995
|
+
payload: {
|
|
1996
|
+
state: "compacted",
|
|
1997
|
+
compaction: {
|
|
1998
|
+
compactionId: codexCompactionId(threadId, turnId),
|
|
1999
|
+
trigger: "automatic"
|
|
2000
|
+
},
|
|
2001
|
+
...params !== void 0 ? { detail: params } : {}
|
|
2002
|
+
},
|
|
1810
2003
|
turnId,
|
|
1811
2004
|
itemId,
|
|
1812
2005
|
method,
|
|
@@ -1815,18 +2008,24 @@ function mapNotificationToEvents(input) {
|
|
|
1815
2008
|
];
|
|
1816
2009
|
}
|
|
1817
2010
|
if (type && normalizeItemType(type).includes("plan")) {
|
|
2011
|
+
const planMarkdown = itemDetail(params);
|
|
2012
|
+
if (!planMarkdown) return [];
|
|
1818
2013
|
return [
|
|
1819
2014
|
baseEvent({
|
|
1820
2015
|
type: "turn.proposed.completed",
|
|
1821
2016
|
threadId,
|
|
1822
2017
|
turnId,
|
|
1823
2018
|
itemId,
|
|
1824
|
-
payload:
|
|
2019
|
+
payload: { planMarkdown },
|
|
1825
2020
|
method,
|
|
1826
2021
|
providerInstanceId
|
|
1827
2022
|
})
|
|
1828
2023
|
];
|
|
1829
2024
|
}
|
|
2025
|
+
const canonical = toCanonicalItemType(type);
|
|
2026
|
+
if (canonical === "unknown") return [];
|
|
2027
|
+
const rawStatus = itemStatusOf(params);
|
|
2028
|
+
const status = rawStatus === "failed" || rawStatus === "declined" ? rawStatus : "completed";
|
|
1830
2029
|
return [
|
|
1831
2030
|
baseEvent({
|
|
1832
2031
|
type: "item.completed",
|
|
@@ -1834,45 +2033,62 @@ function mapNotificationToEvents(input) {
|
|
|
1834
2033
|
turnId,
|
|
1835
2034
|
itemId,
|
|
1836
2035
|
payload: {
|
|
1837
|
-
itemType:
|
|
1838
|
-
|
|
1839
|
-
|
|
2036
|
+
itemType: canonical,
|
|
2037
|
+
status,
|
|
2038
|
+
...itemTitle(params) ? { title: itemTitle(params) } : {},
|
|
2039
|
+
...itemDetail(params) ? { detail: itemDetail(params) } : {},
|
|
2040
|
+
...params !== void 0 ? { data: params } : {}
|
|
1840
2041
|
},
|
|
1841
2042
|
method,
|
|
1842
2043
|
providerInstanceId
|
|
1843
|
-
})
|
|
1844
|
-
...producedImages(params).map((event) => ({ ...event, threadId }))
|
|
2044
|
+
})
|
|
1845
2045
|
];
|
|
1846
2046
|
}
|
|
1847
|
-
case "item/plan/delta":
|
|
2047
|
+
case "item/plan/delta": {
|
|
2048
|
+
const delta = isRecord3(params) ? asString(params["delta"]) : void 0;
|
|
2049
|
+
if (!delta) return [];
|
|
1848
2050
|
return [
|
|
1849
2051
|
baseEvent({
|
|
1850
2052
|
type: "turn.proposed.delta",
|
|
1851
2053
|
threadId,
|
|
1852
2054
|
turnId,
|
|
1853
2055
|
itemId,
|
|
1854
|
-
payload:
|
|
2056
|
+
payload: { delta },
|
|
1855
2057
|
method,
|
|
1856
2058
|
providerInstanceId
|
|
1857
2059
|
})
|
|
1858
2060
|
];
|
|
2061
|
+
}
|
|
1859
2062
|
case "item/agentMessage/delta":
|
|
1860
|
-
case "item/reasoning/
|
|
1861
|
-
case "item/reasoning/
|
|
2063
|
+
case "item/reasoning/textDelta":
|
|
2064
|
+
case "item/reasoning/summaryTextDelta":
|
|
2065
|
+
case "item/commandExecution/outputDelta":
|
|
2066
|
+
case "item/fileChange/outputDelta": {
|
|
1862
2067
|
const delta = isRecord3(params) ? asString(params["delta"]) ?? asString(params["text"]) ?? "" : "";
|
|
2068
|
+
if (delta.length === 0) return [];
|
|
2069
|
+
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";
|
|
2070
|
+
const summaryIndex = method === "item/reasoning/summaryTextDelta" && isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
|
|
2071
|
+
const contentIndex = method === "item/reasoning/textDelta" && isRecord3(params) ? asNumber(params["contentIndex"]) : null;
|
|
1863
2072
|
return [
|
|
1864
2073
|
baseEvent({
|
|
1865
2074
|
type: "content.delta",
|
|
1866
2075
|
threadId,
|
|
1867
2076
|
turnId,
|
|
1868
2077
|
itemId,
|
|
1869
|
-
payload: {
|
|
2078
|
+
payload: {
|
|
2079
|
+
streamKind,
|
|
2080
|
+
delta,
|
|
2081
|
+
...summaryIndex !== null ? { summaryIndex } : {},
|
|
2082
|
+
...contentIndex !== null ? { contentIndex } : {}
|
|
2083
|
+
},
|
|
1870
2084
|
method,
|
|
1871
2085
|
providerInstanceId
|
|
1872
2086
|
})
|
|
1873
2087
|
];
|
|
1874
2088
|
}
|
|
1875
|
-
case "item/reasoning/summaryPartAdded":
|
|
2089
|
+
case "item/reasoning/summaryPartAdded": {
|
|
2090
|
+
const summaryIndex = isRecord3(params) ? asNumber(params["summaryIndex"]) : null;
|
|
2091
|
+
if (summaryIndex === null) return [];
|
|
1876
2092
|
return [
|
|
1877
2093
|
baseEvent({
|
|
1878
2094
|
type: "content.delta",
|
|
@@ -1880,59 +2096,176 @@ function mapNotificationToEvents(input) {
|
|
|
1880
2096
|
turnId,
|
|
1881
2097
|
itemId,
|
|
1882
2098
|
payload: {
|
|
1883
|
-
|
|
2099
|
+
streamKind: "reasoning_summary_text",
|
|
1884
2100
|
delta: "",
|
|
1885
|
-
summaryIndex
|
|
2101
|
+
summaryIndex
|
|
2102
|
+
},
|
|
2103
|
+
method,
|
|
2104
|
+
providerInstanceId
|
|
2105
|
+
})
|
|
2106
|
+
];
|
|
2107
|
+
}
|
|
2108
|
+
case "item/mcpToolCall/progress": {
|
|
2109
|
+
const summary = isRecord3(params) ? trimText(params["message"]) : void 0;
|
|
2110
|
+
if (!summary) return [];
|
|
2111
|
+
return [
|
|
2112
|
+
baseEvent({
|
|
2113
|
+
type: "tool.progress",
|
|
2114
|
+
threadId,
|
|
2115
|
+
turnId,
|
|
2116
|
+
itemId,
|
|
2117
|
+
payload: { summary },
|
|
2118
|
+
method,
|
|
2119
|
+
providerInstanceId
|
|
2120
|
+
})
|
|
2121
|
+
];
|
|
2122
|
+
}
|
|
2123
|
+
case "item/commandExecution/terminalInteraction":
|
|
2124
|
+
return [
|
|
2125
|
+
baseEvent({
|
|
2126
|
+
type: "item.updated",
|
|
2127
|
+
threadId,
|
|
2128
|
+
turnId,
|
|
2129
|
+
itemId,
|
|
2130
|
+
payload: {
|
|
2131
|
+
itemType: "command_execution",
|
|
2132
|
+
...params !== void 0 ? { data: params } : {}
|
|
1886
2133
|
},
|
|
1887
2134
|
method,
|
|
1888
2135
|
providerInstanceId
|
|
1889
2136
|
})
|
|
1890
2137
|
];
|
|
1891
2138
|
case "account/updated":
|
|
2139
|
+
return [
|
|
2140
|
+
baseEvent({
|
|
2141
|
+
type: "account.updated",
|
|
2142
|
+
threadId,
|
|
2143
|
+
payload: { account: params ?? {} },
|
|
2144
|
+
method,
|
|
2145
|
+
providerInstanceId
|
|
2146
|
+
})
|
|
2147
|
+
];
|
|
1892
2148
|
case "account/rateLimits/updated":
|
|
1893
2149
|
return [
|
|
1894
2150
|
baseEvent({
|
|
1895
|
-
type:
|
|
2151
|
+
type: "account.rate-limits.updated",
|
|
1896
2152
|
threadId,
|
|
1897
|
-
payload:
|
|
2153
|
+
payload: {
|
|
2154
|
+
rateLimits: isRecord3(params) && "rateLimits" in params ? params["rateLimits"] : params ?? {}
|
|
2155
|
+
},
|
|
1898
2156
|
method,
|
|
1899
2157
|
providerInstanceId
|
|
1900
2158
|
})
|
|
1901
2159
|
];
|
|
1902
|
-
case "model/rerouted":
|
|
2160
|
+
case "model/rerouted": {
|
|
2161
|
+
const fromModel = isRecord3(params) ? trimText(params["fromModel"]) : void 0;
|
|
2162
|
+
const toModel = isRecord3(params) ? trimText(params["toModel"]) : void 0;
|
|
2163
|
+
const reason = isRecord3(params) ? trimText(params["reason"]) : void 0;
|
|
2164
|
+
if (!fromModel || !toModel || !reason) return [];
|
|
1903
2165
|
return [
|
|
1904
2166
|
baseEvent({
|
|
1905
2167
|
type: "model.rerouted",
|
|
1906
2168
|
threadId,
|
|
1907
|
-
|
|
2169
|
+
turnId,
|
|
2170
|
+
payload: { fromModel, toModel, reason },
|
|
1908
2171
|
method,
|
|
1909
2172
|
providerInstanceId
|
|
1910
2173
|
})
|
|
1911
2174
|
];
|
|
1912
|
-
|
|
2175
|
+
}
|
|
2176
|
+
case "configWarning":
|
|
2177
|
+
case "deprecationNotice": {
|
|
2178
|
+
const summary = isRecord3(params) ? trimText(params["summary"]) : void 0;
|
|
2179
|
+
if (!summary) return [];
|
|
2180
|
+
const details = isRecord3(params) ? trimText(params["details"]) : void 0;
|
|
1913
2181
|
return [
|
|
1914
2182
|
baseEvent({
|
|
1915
|
-
type: "config.warning",
|
|
2183
|
+
type: method === "configWarning" ? "config.warning" : "deprecation.notice",
|
|
1916
2184
|
threadId,
|
|
1917
|
-
payload:
|
|
2185
|
+
payload: {
|
|
2186
|
+
summary,
|
|
2187
|
+
...details ? { details } : {},
|
|
2188
|
+
...method === "configWarning" && isRecord3(params) ? {
|
|
2189
|
+
...trimText(params["path"]) ? { path: trimText(params["path"]) } : {},
|
|
2190
|
+
...params["range"] !== void 0 && params["range"] !== null ? { range: params["range"] } : {}
|
|
2191
|
+
} : {}
|
|
2192
|
+
},
|
|
1918
2193
|
method,
|
|
1919
2194
|
providerInstanceId
|
|
1920
2195
|
})
|
|
1921
2196
|
];
|
|
1922
|
-
|
|
2197
|
+
}
|
|
2198
|
+
case "mcpServer/oauthLogin/completed": {
|
|
2199
|
+
const success = isRecord3(params) ? params["success"] : void 0;
|
|
2200
|
+
if (typeof success !== "boolean") return [];
|
|
2201
|
+
const name = isRecord3(params) ? trimText(params["name"]) : void 0;
|
|
2202
|
+
const error = isRecord3(params) ? trimText(params["error"]) : void 0;
|
|
1923
2203
|
return [
|
|
1924
2204
|
baseEvent({
|
|
1925
2205
|
type: "mcp.oauth.completed",
|
|
1926
2206
|
threadId,
|
|
1927
|
-
payload:
|
|
2207
|
+
payload: {
|
|
2208
|
+
success,
|
|
2209
|
+
...name ? { name } : {},
|
|
2210
|
+
...error ? { error } : {}
|
|
2211
|
+
},
|
|
1928
2212
|
method,
|
|
1929
2213
|
providerInstanceId
|
|
1930
2214
|
})
|
|
1931
2215
|
];
|
|
2216
|
+
}
|
|
1932
2217
|
default:
|
|
1933
2218
|
return [];
|
|
1934
2219
|
}
|
|
1935
2220
|
}
|
|
2221
|
+
function toRequestTypeFromKind(kind) {
|
|
2222
|
+
switch (kind) {
|
|
2223
|
+
case "command":
|
|
2224
|
+
return "command_execution_approval";
|
|
2225
|
+
case "file-read":
|
|
2226
|
+
return "file_read_approval";
|
|
2227
|
+
case "file-change":
|
|
2228
|
+
return "file_change_approval";
|
|
2229
|
+
case "apply-patch":
|
|
2230
|
+
return "apply_patch_approval";
|
|
2231
|
+
case "mcp-elicitation":
|
|
2232
|
+
return "mcp_elicitation_approval";
|
|
2233
|
+
default:
|
|
2234
|
+
return "unknown";
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
function toRequestTypeFromMethod(method) {
|
|
2238
|
+
switch (method) {
|
|
2239
|
+
case "item/commandExecution/requestApproval":
|
|
2240
|
+
return "command_execution_approval";
|
|
2241
|
+
case "execCommandApproval":
|
|
2242
|
+
return "exec_command_approval";
|
|
2243
|
+
case "item/fileRead/requestApproval":
|
|
2244
|
+
return "file_read_approval";
|
|
2245
|
+
case "item/fileChange/requestApproval":
|
|
2246
|
+
return "file_change_approval";
|
|
2247
|
+
case "applyPatchApproval":
|
|
2248
|
+
return "apply_patch_approval";
|
|
2249
|
+
case "mcpServer/elicitation/request":
|
|
2250
|
+
return "mcp_elicitation_approval";
|
|
2251
|
+
case "item/tool/requestUserInput":
|
|
2252
|
+
return "tool_user_input";
|
|
2253
|
+
case "item/tool/call":
|
|
2254
|
+
return "dynamic_tool_call";
|
|
2255
|
+
default:
|
|
2256
|
+
return "unknown";
|
|
2257
|
+
}
|
|
2258
|
+
}
|
|
2259
|
+
function elicitationOptions(params) {
|
|
2260
|
+
const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
|
|
2261
|
+
return [
|
|
2262
|
+
{ decision: "cancel", label: "Cancel" },
|
|
2263
|
+
{ decision: "decline", label: "Decline" },
|
|
2264
|
+
...persist === "session" ? [{ decision: "acceptForSession", label: "Always allow this session" }] : [],
|
|
2265
|
+
...persist === "always" ? [{ decision: "acceptAlways", label: "Always allow" }] : [],
|
|
2266
|
+
{ decision: "accept", label: "Approve" }
|
|
2267
|
+
];
|
|
2268
|
+
}
|
|
1936
2269
|
function mapServerRequestToEvents(input) {
|
|
1937
2270
|
const requestId = String(input.id);
|
|
1938
2271
|
if (input.method === "item/tool/requestUserInput") {
|
|
@@ -1950,18 +2283,44 @@ function mapServerRequestToEvents(input) {
|
|
|
1950
2283
|
})
|
|
1951
2284
|
];
|
|
1952
2285
|
}
|
|
1953
|
-
const
|
|
1954
|
-
const
|
|
2286
|
+
const requestType = toRequestTypeFromMethod(input.method);
|
|
2287
|
+
const payload = {
|
|
2288
|
+
requestType,
|
|
2289
|
+
// Codex 的审批回复只有一个枚举,理由随下一条 prompt 送达。
|
|
2290
|
+
declineReasonRelay: "queued"
|
|
2291
|
+
};
|
|
2292
|
+
if (input.method === "mcpServer/elicitation/request") {
|
|
2293
|
+
const described = describeMcpElicitation(input.params);
|
|
2294
|
+
const appName = described.appName ?? described.serverName ?? void 0;
|
|
2295
|
+
payload["options"] = elicitationOptions(input.params);
|
|
2296
|
+
if (appName) payload["appName"] = appName;
|
|
2297
|
+
if (described.message) payload["detail"] = described.message;
|
|
2298
|
+
payload["args"] = input.params;
|
|
2299
|
+
} else if (input.method === "item/fileChange/requestApproval" || input.method.includes("applyPatch")) {
|
|
2300
|
+
const described = describeFileChangeApproval(input.params);
|
|
2301
|
+
if (described.reason) payload["detail"] = described.reason;
|
|
2302
|
+
if (described.fileChanges.length > 0) payload["fileChanges"] = described.fileChanges;
|
|
2303
|
+
payload["args"] = {
|
|
2304
|
+
...described.callId ? { callId: described.callId } : {},
|
|
2305
|
+
...described.reason ? { reason: described.reason } : {},
|
|
2306
|
+
...described.grantRoot ? { grantRoot: described.grantRoot } : {}
|
|
2307
|
+
};
|
|
2308
|
+
} else {
|
|
2309
|
+
const described = describeCommandApproval(input.params);
|
|
2310
|
+
const detail = described.command || described.reason;
|
|
2311
|
+
if (detail) payload["detail"] = detail;
|
|
2312
|
+
payload["args"] = {
|
|
2313
|
+
command: described.command,
|
|
2314
|
+
...described.cwd ? { cwd: described.cwd } : {},
|
|
2315
|
+
...described.reason ? { reason: described.reason } : {}
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
1955
2318
|
return [
|
|
1956
2319
|
baseEvent({
|
|
1957
2320
|
type: "request.opened",
|
|
1958
2321
|
threadId: input.threadId,
|
|
1959
2322
|
requestId,
|
|
1960
|
-
payload
|
|
1961
|
-
kind,
|
|
1962
|
-
args,
|
|
1963
|
-
declineReasonRelay: "queued"
|
|
1964
|
-
},
|
|
2323
|
+
payload,
|
|
1965
2324
|
method: input.method,
|
|
1966
2325
|
source: "codex.app-server.request",
|
|
1967
2326
|
providerInstanceId: input.providerInstanceId
|
|
@@ -1990,12 +2349,15 @@ var CodexSessionRuntime = class {
|
|
|
1990
2349
|
activeTurnId;
|
|
1991
2350
|
model;
|
|
1992
2351
|
runtimeMode;
|
|
2352
|
+
/** 宿主解好的这一轮的档(座位画的就是它)。缺席 = 宿主没给,按 `runtimeMode` 查表。 */
|
|
2353
|
+
nativeModeId;
|
|
1993
2354
|
interactionMode;
|
|
1994
2355
|
closed = false;
|
|
1995
2356
|
closeCompleted = false;
|
|
1996
2357
|
turns = [];
|
|
1997
2358
|
pendingApprovals = /* @__PURE__ */ new Map();
|
|
1998
2359
|
pendingUserInputs = /* @__PURE__ */ new Map();
|
|
2360
|
+
requestKinds = /* @__PURE__ */ new Map();
|
|
1999
2361
|
writeAcks = /* @__PURE__ */ new Map();
|
|
2000
2362
|
grants;
|
|
2001
2363
|
declineRelay;
|
|
@@ -2007,6 +2369,16 @@ var CodexSessionRuntime = class {
|
|
|
2007
2369
|
host;
|
|
2008
2370
|
rpc;
|
|
2009
2371
|
emit;
|
|
2372
|
+
/** 这一轮该用哪套三字段组合:只由这一轮的档决定(见 `codexNativeModeFor`)。 */
|
|
2373
|
+
#threadConfig() {
|
|
2374
|
+
return codexThreadConfigForNativeMode(codexNativeModeFor(this.runtimeMode, this.nativeModeId));
|
|
2375
|
+
}
|
|
2376
|
+
/** 同上,沙箱策略那一位。 */
|
|
2377
|
+
#turnSandboxPolicy() {
|
|
2378
|
+
return codexTurnSandboxPolicyForNativeMode(
|
|
2379
|
+
codexNativeModeFor(this.runtimeMode, this.nativeModeId)
|
|
2380
|
+
);
|
|
2381
|
+
}
|
|
2010
2382
|
constructor(host, rpc, emit, input, cwd, appServerArgs, grants, declineRelay) {
|
|
2011
2383
|
this.host = host;
|
|
2012
2384
|
this.rpc = rpc;
|
|
@@ -2014,6 +2386,7 @@ var CodexSessionRuntime = class {
|
|
|
2014
2386
|
this.threadId = input.threadId;
|
|
2015
2387
|
this.cwd = cwd;
|
|
2016
2388
|
this.runtimeMode = input.runtimeMode;
|
|
2389
|
+
this.nativeModeId = input.nativeModeId;
|
|
2017
2390
|
this.appServerArgs = appServerArgs;
|
|
2018
2391
|
this.grants = grants ?? createApprovalGrantStore();
|
|
2019
2392
|
this.declineRelay = declineRelay ?? createDeclineReasonRelay();
|
|
@@ -2023,6 +2396,7 @@ var CodexSessionRuntime = class {
|
|
|
2023
2396
|
providerInstanceId: host.instanceId,
|
|
2024
2397
|
status: "idle",
|
|
2025
2398
|
runtimeMode: input.runtimeMode,
|
|
2399
|
+
nativeModeId: codexNativeModeFor(input.runtimeMode, input.nativeModeId),
|
|
2026
2400
|
cwd,
|
|
2027
2401
|
threadId: input.threadId,
|
|
2028
2402
|
createdAt,
|
|
@@ -2102,6 +2476,24 @@ var CodexSessionRuntime = class {
|
|
|
2102
2476
|
this.dispatchMapped(method, params);
|
|
2103
2477
|
}
|
|
2104
2478
|
dispatchMapped(method, params) {
|
|
2479
|
+
if (method === "serverRequest/resolved") {
|
|
2480
|
+
const resolvedId = (isRecord3(params) ? asString(params["requestId"]) : null) ?? void 0;
|
|
2481
|
+
if (resolvedId !== void 0) {
|
|
2482
|
+
this.emit({
|
|
2483
|
+
eventId: `codex-req-resolved-${resolvedId}`,
|
|
2484
|
+
provider: CODEX_DRIVER_KIND,
|
|
2485
|
+
threadId: this.threadId,
|
|
2486
|
+
createdAt: nowIso(),
|
|
2487
|
+
type: "request.resolved",
|
|
2488
|
+
requestId: resolvedId,
|
|
2489
|
+
payload: {
|
|
2490
|
+
requestType: toRequestTypeFromKind(this.requestKinds.get(resolvedId)),
|
|
2491
|
+
resolution: params
|
|
2492
|
+
}
|
|
2493
|
+
});
|
|
2494
|
+
}
|
|
2495
|
+
return;
|
|
2496
|
+
}
|
|
2105
2497
|
for (const event of mapNotificationToEvents({
|
|
2106
2498
|
method,
|
|
2107
2499
|
params,
|
|
@@ -2280,6 +2672,7 @@ var CodexSessionRuntime = class {
|
|
|
2280
2672
|
threadId: this.threadId,
|
|
2281
2673
|
providerInstanceId: this.host.instanceId
|
|
2282
2674
|
});
|
|
2675
|
+
this.requestKinds.set(requestId, kind);
|
|
2283
2676
|
await new Promise((resolve2) => {
|
|
2284
2677
|
this.pendingApprovals.set(requestId, {
|
|
2285
2678
|
jsonRpcId: id,
|
|
@@ -2319,14 +2712,22 @@ var CodexSessionRuntime = class {
|
|
|
2319
2712
|
createdAt: nowIso(),
|
|
2320
2713
|
type: "request.resolved",
|
|
2321
2714
|
requestId,
|
|
2322
|
-
|
|
2715
|
+
// requestType 是契约必填;缺了它宿主整条解不动,卡片停在「等待决议」。
|
|
2716
|
+
payload: {
|
|
2717
|
+
requestType: toRequestTypeFromKind(pending.kind),
|
|
2718
|
+
decision,
|
|
2719
|
+
...reason ? { reason } : {}
|
|
2720
|
+
}
|
|
2323
2721
|
});
|
|
2324
2722
|
}
|
|
2325
2723
|
async respondToUserInput(requestId, answers) {
|
|
2326
2724
|
const pending = this.pendingUserInputs.get(requestId);
|
|
2327
2725
|
if (!pending) throw new Error(`unknown user-input ${requestId}`);
|
|
2328
2726
|
const mapped = toCodexUserInputAnswers(answers);
|
|
2329
|
-
|
|
2727
|
+
if (mapped === void 0) {
|
|
2728
|
+
throw new Error(`invalid user input answers for request ${requestId}`);
|
|
2729
|
+
}
|
|
2730
|
+
pending.settle({ answers: mapped });
|
|
2330
2731
|
this.emit({
|
|
2331
2732
|
eventId: `codex-ui-${requestId}`,
|
|
2332
2733
|
provider: CODEX_DRIVER_KIND,
|
|
@@ -2334,7 +2735,8 @@ var CodexSessionRuntime = class {
|
|
|
2334
2735
|
createdAt: nowIso(),
|
|
2335
2736
|
type: "user-input.resolved",
|
|
2336
2737
|
requestId,
|
|
2337
|
-
|
|
2738
|
+
// 契约的 answers 是 questionId → 答案 的 record,不是数组。
|
|
2739
|
+
payload: { answers: toCanonicalUserInputAnswers(mapped) }
|
|
2338
2740
|
});
|
|
2339
2741
|
}
|
|
2340
2742
|
async withActiveWriterRecovery(run, allowUnsubscribe) {
|
|
@@ -2368,7 +2770,7 @@ var CodexSessionRuntime = class {
|
|
|
2368
2770
|
throw lastError instanceof Error ? lastError : new Error(String(lastError));
|
|
2369
2771
|
}
|
|
2370
2772
|
threadConfigParams() {
|
|
2371
|
-
const config =
|
|
2773
|
+
const config = this.#threadConfig();
|
|
2372
2774
|
return {
|
|
2373
2775
|
approvalPolicy: config.approvalPolicy,
|
|
2374
2776
|
sandbox: config.sandbox,
|
|
@@ -2419,7 +2821,7 @@ var CodexSessionRuntime = class {
|
|
|
2419
2821
|
threadId: this.threadId,
|
|
2420
2822
|
createdAt: nowIso(),
|
|
2421
2823
|
type: "session.started",
|
|
2422
|
-
payload: {
|
|
2824
|
+
payload: { resume: this.session.resumeCursor }
|
|
2423
2825
|
});
|
|
2424
2826
|
return this.session;
|
|
2425
2827
|
}
|
|
@@ -2451,8 +2853,8 @@ var CodexSessionRuntime = class {
|
|
|
2451
2853
|
if (hasConfiguredMcpServer(this.appServerArgs)) {
|
|
2452
2854
|
await this.rpc.request("mcpServer/reload", {}, 0).catch(() => void 0);
|
|
2453
2855
|
}
|
|
2454
|
-
const sandboxPolicy =
|
|
2455
|
-
const config =
|
|
2856
|
+
const sandboxPolicy = this.#turnSandboxPolicy();
|
|
2857
|
+
const config = this.#threadConfig();
|
|
2456
2858
|
const developerInstructions = this.developerInstructions(input);
|
|
2457
2859
|
const requestedModel = modelIdFromSelection(input.modelSelection) ?? this.model;
|
|
2458
2860
|
const params = {
|
|
@@ -2464,15 +2866,22 @@ var CodexSessionRuntime = class {
|
|
|
2464
2866
|
...requestedModel ? { model: requestedModel } : {},
|
|
2465
2867
|
...developerInstructions ? { developerInstructions } : {}
|
|
2466
2868
|
};
|
|
2467
|
-
|
|
2468
|
-
|
|
2869
|
+
const interactionMode = typeof input.interactionMode === "string" ? input.interactionMode : void 0;
|
|
2870
|
+
if (interactionMode !== void 0) {
|
|
2871
|
+
params["collaborationMode"] = codexCollaborationMode({
|
|
2872
|
+
interactionMode,
|
|
2873
|
+
model: requestedModel ?? DEFAULT_MODEL,
|
|
2874
|
+
reasoningEffort: modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? void 0
|
|
2875
|
+
});
|
|
2469
2876
|
}
|
|
2470
2877
|
const isSteer = input.expectedTurnId !== void 0;
|
|
2471
2878
|
const result = isSteer ? await this.rpc.request(
|
|
2472
2879
|
"turn/steer",
|
|
2473
2880
|
{
|
|
2474
2881
|
...params,
|
|
2475
|
-
turnId
|
|
2882
|
+
// 这一位的名字是 `expectedTurnId`,不是 `turnId`:0.155.1 少了它整条拒收
|
|
2883
|
+
// (`Invalid request: missing field \`expectedTurnId\``),插队的那句话发不出去。
|
|
2884
|
+
expectedTurnId: input.expectedTurnId
|
|
2476
2885
|
},
|
|
2477
2886
|
0
|
|
2478
2887
|
) : await this.withActiveWriterRecovery(() => this.rpc.request("turn/start", params, 0), false);
|
|
@@ -2569,6 +2978,7 @@ var CodexSessionRuntime = class {
|
|
|
2569
2978
|
async close() {
|
|
2570
2979
|
if (this.closeCompleted) return;
|
|
2571
2980
|
this.closed = true;
|
|
2981
|
+
this.requestKinds.clear();
|
|
2572
2982
|
this.failPendingNativeWrites("session closed before write confirmation");
|
|
2573
2983
|
this.settlePendingApprovals("cancel");
|
|
2574
2984
|
this.settlePendingUserInputs({});
|
|
@@ -2792,6 +3202,198 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
|
|
|
2792
3202
|
return model;
|
|
2793
3203
|
}
|
|
2794
3204
|
|
|
3205
|
+
// src/mcpOAuth.ts
|
|
3206
|
+
async function startCodexMcpOAuth(input) {
|
|
3207
|
+
if (input.recipe.binaryPath.length === 0) {
|
|
3208
|
+
throw new Error("Codex program path is not configured");
|
|
3209
|
+
}
|
|
3210
|
+
const mcp = codexMcpLaunchConfig([input.server]);
|
|
3211
|
+
const env = buildCodexSpawnEnv({
|
|
3212
|
+
recipe: input.recipe,
|
|
3213
|
+
layout: input.layout,
|
|
3214
|
+
launchEnv: { ...input.processEnv, ...mcp.environment },
|
|
3215
|
+
crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
|
|
3216
|
+
});
|
|
3217
|
+
const handle = await input.host.process.spawn({
|
|
3218
|
+
program: input.recipe.binaryPath,
|
|
3219
|
+
args: [...tokenizeLaunchArgs(input.recipe.launchArgs), ...mcp.args],
|
|
3220
|
+
cwd: input.cwd,
|
|
3221
|
+
owner: { bindingId: input.host.bindingId },
|
|
3222
|
+
env
|
|
3223
|
+
});
|
|
3224
|
+
let settle;
|
|
3225
|
+
const completed = new Promise((resolve2) => {
|
|
3226
|
+
settle = resolve2;
|
|
3227
|
+
});
|
|
3228
|
+
const rpc = await createCodexRpcClient(handle, {
|
|
3229
|
+
onNotification: (notification) => {
|
|
3230
|
+
if (notification.method !== "mcpServer/oauthLogin/completed") return;
|
|
3231
|
+
const params = isRecord3(notification.params) ? notification.params : {};
|
|
3232
|
+
if (asString(params["name"]) !== input.server.name) return;
|
|
3233
|
+
const error = asString(params["error"]);
|
|
3234
|
+
settle?.({
|
|
3235
|
+
success: params["success"] === true,
|
|
3236
|
+
...error === null || error === void 0 ? {} : { error }
|
|
3237
|
+
});
|
|
3238
|
+
},
|
|
3239
|
+
onServerRequest: () => void 0,
|
|
3240
|
+
onStderrLine: () => void 0
|
|
3241
|
+
});
|
|
3242
|
+
const close = async () => {
|
|
3243
|
+
settle?.({ success: false, error: "cancelled" });
|
|
3244
|
+
await rpc.close().catch(() => void 0);
|
|
3245
|
+
};
|
|
3246
|
+
try {
|
|
3247
|
+
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
3248
|
+
await rpc.notify("initialized");
|
|
3249
|
+
const response = await rpc.request("mcpServer/oauth/login", {
|
|
3250
|
+
name: input.server.name,
|
|
3251
|
+
...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
|
|
3252
|
+
});
|
|
3253
|
+
const authorizationUrl = isRecord3(response) ? asString(response["authorizationUrl"]) : void 0;
|
|
3254
|
+
if (!authorizationUrl) {
|
|
3255
|
+
await close();
|
|
3256
|
+
throw new Error(`Codex did not return an authorization URL for '${input.server.name}'.`);
|
|
3257
|
+
}
|
|
3258
|
+
return { authorizationUrl, handle, completed, close };
|
|
3259
|
+
} catch (error) {
|
|
3260
|
+
await close();
|
|
3261
|
+
throw error;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
// src/models.ts
|
|
3266
|
+
var MODEL_LIST_MAX_PAGES = 20;
|
|
3267
|
+
var MODEL_LIST_TIMEOUT_MS = 15e3;
|
|
3268
|
+
var REASONING_EFFORT_LABELS = {
|
|
3269
|
+
none: "None",
|
|
3270
|
+
minimal: "Minimal",
|
|
3271
|
+
low: "Low",
|
|
3272
|
+
medium: "Medium",
|
|
3273
|
+
high: "High",
|
|
3274
|
+
xhigh: "Extra High",
|
|
3275
|
+
max: "Max",
|
|
3276
|
+
ultra: "Ultra"
|
|
3277
|
+
};
|
|
3278
|
+
var DEFAULT_SERVICE_TIER_ID = "default";
|
|
3279
|
+
var reasoningEffortLabel = (effort) => REASONING_EFFORT_LABELS[effort] ?? effort;
|
|
3280
|
+
function codexModelDisplayName(displayName) {
|
|
3281
|
+
return displayName.replace(/^gpt/i, "GPT").replace(/-([a-z])/g, (_, c) => `-${c.toUpperCase()}`);
|
|
3282
|
+
}
|
|
3283
|
+
function reasoningDescriptor(row) {
|
|
3284
|
+
const supported = row["supportedReasoningEfforts"];
|
|
3285
|
+
if (!Array.isArray(supported) || supported.length === 0) return null;
|
|
3286
|
+
const defaultEffort = asString(row["defaultReasoningEffort"]);
|
|
3287
|
+
const options = supported.flatMap((entry) => {
|
|
3288
|
+
if (!isRecord3(entry)) return [];
|
|
3289
|
+
const id = asString(entry["reasoningEffort"]);
|
|
3290
|
+
if (!id) return [];
|
|
3291
|
+
const description = asString(entry["description"]);
|
|
3292
|
+
return [
|
|
3293
|
+
{
|
|
3294
|
+
id,
|
|
3295
|
+
label: reasoningEffortLabel(id),
|
|
3296
|
+
...description ? { description } : {},
|
|
3297
|
+
...id === defaultEffort ? { isDefault: true } : {}
|
|
3298
|
+
}
|
|
3299
|
+
];
|
|
3300
|
+
});
|
|
3301
|
+
if (options.length === 0) return null;
|
|
3302
|
+
return {
|
|
3303
|
+
id: "reasoningEffort",
|
|
3304
|
+
label: "Reasoning",
|
|
3305
|
+
type: "select",
|
|
3306
|
+
options,
|
|
3307
|
+
...defaultEffort ? { currentValue: defaultEffort } : {}
|
|
3308
|
+
};
|
|
3309
|
+
}
|
|
3310
|
+
function serviceTierDescriptor(row) {
|
|
3311
|
+
const declared = Array.isArray(row["serviceTiers"]) ? row["serviceTiers"] : [];
|
|
3312
|
+
const tiers = declared.length > 0 ? declared.flatMap((entry) => {
|
|
3313
|
+
if (!isRecord3(entry)) return [];
|
|
3314
|
+
const id = asString(entry["id"]);
|
|
3315
|
+
if (!id) return [];
|
|
3316
|
+
const name = asString(entry["name"]);
|
|
3317
|
+
const description = asString(entry["description"]);
|
|
3318
|
+
return [{ id, name: name ?? id, ...description ? { description } : {} }];
|
|
3319
|
+
}) : (Array.isArray(row["additionalSpeedTiers"]) ? row["additionalSpeedTiers"] : []).flatMap(
|
|
3320
|
+
(entry) => {
|
|
3321
|
+
const id = typeof entry === "string" ? entry : null;
|
|
3322
|
+
return id === null ? [] : [{ id, name: id === "fast" ? "Fast" : id }];
|
|
3323
|
+
}
|
|
3324
|
+
);
|
|
3325
|
+
if (tiers.length === 0) return null;
|
|
3326
|
+
const declaredDefault = asString(row["defaultServiceTier"]);
|
|
3327
|
+
const defaultTier = declaredDefault && tiers.some((tier) => tier.id === declaredDefault) ? declaredDefault : DEFAULT_SERVICE_TIER_ID;
|
|
3328
|
+
return {
|
|
3329
|
+
id: "serviceTier",
|
|
3330
|
+
label: "Service Tier",
|
|
3331
|
+
type: "select",
|
|
3332
|
+
options: [
|
|
3333
|
+
{
|
|
3334
|
+
id: DEFAULT_SERVICE_TIER_ID,
|
|
3335
|
+
label: "Standard",
|
|
3336
|
+
...defaultTier === DEFAULT_SERVICE_TIER_ID ? { isDefault: true } : {}
|
|
3337
|
+
},
|
|
3338
|
+
...tiers.map((tier) => ({
|
|
3339
|
+
id: tier.id,
|
|
3340
|
+
label: tier.name,
|
|
3341
|
+
...tier.description ? { description: tier.description } : {},
|
|
3342
|
+
...defaultTier === tier.id ? { isDefault: true } : {}
|
|
3343
|
+
}))
|
|
3344
|
+
],
|
|
3345
|
+
currentValue: defaultTier
|
|
3346
|
+
};
|
|
3347
|
+
}
|
|
3348
|
+
function isSupersededModel(row) {
|
|
3349
|
+
if (isRecord3(row["upgradeInfo"])) return true;
|
|
3350
|
+
return (asString(row["upgrade"]) ?? "").length > 0;
|
|
3351
|
+
}
|
|
3352
|
+
function codexProbeModel(row) {
|
|
3353
|
+
if (!isRecord3(row)) return null;
|
|
3354
|
+
const slug = asString(row["model"]);
|
|
3355
|
+
if (!slug) return null;
|
|
3356
|
+
const displayName = asString(row["displayName"]);
|
|
3357
|
+
const optionDescriptors = [reasoningDescriptor(row), serviceTierDescriptor(row)].filter(
|
|
3358
|
+
(descriptor) => descriptor !== null
|
|
3359
|
+
);
|
|
3360
|
+
return {
|
|
3361
|
+
slug,
|
|
3362
|
+
...displayName ? { name: codexModelDisplayName(displayName) } : {},
|
|
3363
|
+
...row["isDefault"] === true ? { isDefault: true } : {},
|
|
3364
|
+
...isSupersededModel(row) ? { isLegacy: true } : {},
|
|
3365
|
+
...row["hidden"] === true ? { hidden: true } : {},
|
|
3366
|
+
// 空表也给 `capabilities`:`{}` 与 `null` 对宿主是一回事(都不画那一格),
|
|
3367
|
+
// 但给对象保住「这只手确实答过这一问」这层意思。
|
|
3368
|
+
capabilities: { optionDescriptors }
|
|
3369
|
+
};
|
|
3370
|
+
}
|
|
3371
|
+
async function listCodexModels(rpc) {
|
|
3372
|
+
const models = [];
|
|
3373
|
+
let cursor;
|
|
3374
|
+
for (let page = 0; page < MODEL_LIST_MAX_PAGES; page++) {
|
|
3375
|
+
let response;
|
|
3376
|
+
try {
|
|
3377
|
+
response = await rpc.request(
|
|
3378
|
+
"model/list",
|
|
3379
|
+
cursor === void 0 ? {} : { cursor },
|
|
3380
|
+
MODEL_LIST_TIMEOUT_MS
|
|
3381
|
+
);
|
|
3382
|
+
} catch {
|
|
3383
|
+
return void 0;
|
|
3384
|
+
}
|
|
3385
|
+
if (!isRecord3(response) || !Array.isArray(response["data"])) return void 0;
|
|
3386
|
+
for (const row of response["data"]) {
|
|
3387
|
+
const model = codexProbeModel(row);
|
|
3388
|
+
if (model !== null) models.push(model);
|
|
3389
|
+
}
|
|
3390
|
+
const next = asString(response["nextCursor"]);
|
|
3391
|
+
if (!next || next === cursor) return models;
|
|
3392
|
+
cursor = next;
|
|
3393
|
+
}
|
|
3394
|
+
return models;
|
|
3395
|
+
}
|
|
3396
|
+
|
|
2795
3397
|
// src/signIn.ts
|
|
2796
3398
|
var DEVICE_CODE_URL = /https?:\/\/\S+/;
|
|
2797
3399
|
var DEVICE_CODE_VALUE = /\b([A-Z0-9]{4,}(?:-[A-Z0-9]{4,})+)\b/;
|
|
@@ -2947,22 +3549,26 @@ async function verifyCodexSignIn(input) {
|
|
|
2947
3549
|
installed: true,
|
|
2948
3550
|
version: null,
|
|
2949
3551
|
readiness: "unchecked",
|
|
2950
|
-
auth: { status: "unauthenticated" }
|
|
3552
|
+
auth: { status: "unauthenticated" },
|
|
3553
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2951
3554
|
};
|
|
2952
3555
|
}
|
|
2953
|
-
|
|
3556
|
+
const models = await listCodexModels(rpc);
|
|
2954
3557
|
return {
|
|
2955
3558
|
installed: true,
|
|
2956
3559
|
version: null,
|
|
2957
3560
|
readiness: "ready",
|
|
2958
|
-
auth
|
|
3561
|
+
auth,
|
|
3562
|
+
...models === void 0 ? {} : { models },
|
|
3563
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2959
3564
|
};
|
|
2960
3565
|
} catch {
|
|
2961
3566
|
return {
|
|
2962
3567
|
installed: true,
|
|
2963
3568
|
version: null,
|
|
2964
3569
|
readiness: "unchecked",
|
|
2965
|
-
auth: { status: "unknown" }
|
|
3570
|
+
auth: { status: "unknown" },
|
|
3571
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
2966
3572
|
};
|
|
2967
3573
|
} finally {
|
|
2968
3574
|
await rpc.close().catch(() => void 0);
|
|
@@ -2973,6 +3579,10 @@ async function verifyCodexSignIn(input) {
|
|
|
2973
3579
|
import * as NodeFSP2 from "node:fs/promises";
|
|
2974
3580
|
import * as NodeOS3 from "node:os";
|
|
2975
3581
|
import * as NodePath5 from "node:path";
|
|
3582
|
+
import {
|
|
3583
|
+
mergeLaunchEnv as mergeLaunchEnv2,
|
|
3584
|
+
brainSlotEnvKeys as brainSlotEnvKeys2
|
|
3585
|
+
} from "@ycodium-ai/plugin-api/executor";
|
|
2976
3586
|
var CODEX_TIMEOUT_MS = 18e4;
|
|
2977
3587
|
var EARLIER_CONTENT_TRUNCATION_MARKER = "[Earlier content truncated]\n\n";
|
|
2978
3588
|
function limitSection(value, maxChars) {
|
|
@@ -3145,14 +3755,12 @@ function createCodexTextGeneration(input) {
|
|
|
3145
3755
|
const reasoningEffort = modelSelectionStringOption(modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
|
|
3146
3756
|
const serviceTier = modelSelectionStringOption(modelSelection, "serviceTier");
|
|
3147
3757
|
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
|
-
}
|
|
3758
|
+
const env = mergeLaunchEnv2({
|
|
3759
|
+
brainSlots: brainSlotEnvKeys2(CODEX_CARD),
|
|
3760
|
+
accountHome: input.layout.effectiveHomePath ? { CODEX_HOME: input.layout.effectiveHomePath } : input.recipe.homePath.trim() ? { CODEX_HOME: expandHomePath(input.recipe.homePath) } : {},
|
|
3761
|
+
instanceTable: input.recipe.environment,
|
|
3762
|
+
launch: processEnv
|
|
3763
|
+
});
|
|
3156
3764
|
const args = [
|
|
3157
3765
|
"exec",
|
|
3158
3766
|
...codexExecLaunchArgs(launchArgs),
|
|
@@ -3469,8 +4077,11 @@ function accountToAuth2(account) {
|
|
|
3469
4077
|
return { status: "unknown" };
|
|
3470
4078
|
}
|
|
3471
4079
|
}
|
|
3472
|
-
function layoutFor(recipe, launchHome) {
|
|
4080
|
+
function layoutFor(recipe, launchHome, nativeSubscription = false) {
|
|
3473
4081
|
const environmentHome = recipe.environment["CODEX_HOME"];
|
|
4082
|
+
if (nativeSubscription && launchHome?.trim()) {
|
|
4083
|
+
return resolveCodexHomeLayout({ homePath: launchHome.trim(), shadowHomePath: "" });
|
|
4084
|
+
}
|
|
3474
4085
|
return resolveCodexHomeLayout({
|
|
3475
4086
|
homePath: launchHome?.trim() || recipe.homePath,
|
|
3476
4087
|
shadowHomePath: recipe.shadowHomePath,
|
|
@@ -3490,6 +4101,38 @@ var createCodexInstance = (config, host) => {
|
|
|
3490
4101
|
return booked;
|
|
3491
4102
|
};
|
|
3492
4103
|
const probeAuth = () => accountToAuth2(readCodexAccount(accountHomeFromLayout(layoutFor(recipe))));
|
|
4104
|
+
const probeModels = async () => {
|
|
4105
|
+
const layout = layoutFor(recipe);
|
|
4106
|
+
const env = buildCodexSpawnEnv({
|
|
4107
|
+
recipe,
|
|
4108
|
+
layout,
|
|
4109
|
+
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
4110
|
+
});
|
|
4111
|
+
let handle;
|
|
4112
|
+
let rpc;
|
|
4113
|
+
try {
|
|
4114
|
+
handle = await host.process.spawn({
|
|
4115
|
+
program: recipe.binaryPath,
|
|
4116
|
+
args: [...codexSpawnArgs(recipe, layout)],
|
|
4117
|
+
owner: { bindingId: host.bindingId },
|
|
4118
|
+
...Object.keys(env).length > 0 ? { env } : {}
|
|
4119
|
+
});
|
|
4120
|
+
trackHandle(handle, `${recipe.binaryPath} (probe)`);
|
|
4121
|
+
rpc = await createCodexRpcClient(handle, {
|
|
4122
|
+
onNotification: () => void 0,
|
|
4123
|
+
onServerRequest: () => void 0,
|
|
4124
|
+
onStderrLine: () => void 0
|
|
4125
|
+
});
|
|
4126
|
+
await rpc.request("initialize", CODEX_INITIALIZE_PARAMS);
|
|
4127
|
+
await rpc.notify("initialized");
|
|
4128
|
+
return await listCodexModels(rpc);
|
|
4129
|
+
} catch {
|
|
4130
|
+
return void 0;
|
|
4131
|
+
} finally {
|
|
4132
|
+
await rpc?.close().catch(() => void 0);
|
|
4133
|
+
await handle?.kill("SIGTERM").catch(() => void 0);
|
|
4134
|
+
}
|
|
4135
|
+
};
|
|
3493
4136
|
const trackHandle = (handle, command, threadId) => {
|
|
3494
4137
|
live.set(handle.pid, { command, ...threadId === void 0 ? {} : { threadId } });
|
|
3495
4138
|
void handle.exited.finally(() => {
|
|
@@ -3517,14 +4160,20 @@ var createCodexInstance = (config, host) => {
|
|
|
3517
4160
|
version: null,
|
|
3518
4161
|
readiness: "error",
|
|
3519
4162
|
auth: probeAuth(),
|
|
3520
|
-
detail: connectionError
|
|
4163
|
+
detail: connectionError,
|
|
4164
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
3521
4165
|
};
|
|
3522
4166
|
}
|
|
4167
|
+
const models = await probeModels();
|
|
3523
4168
|
return {
|
|
3524
4169
|
installed: true,
|
|
3525
4170
|
version: null,
|
|
3526
|
-
readiness: "unchecked",
|
|
3527
|
-
auth: probeAuth()
|
|
4171
|
+
readiness: models === void 0 ? "unchecked" : "ready",
|
|
4172
|
+
auth: probeAuth(),
|
|
4173
|
+
...models === void 0 ? {} : { models },
|
|
4174
|
+
// 宿主拿的是这一份。登录核验那份(`verifyCodexSignIn`)报了档、这里没报,
|
|
4175
|
+
// 界面就一直画宿主四档——2026-09-20 真发生过,浏览器里才看得见。
|
|
4176
|
+
...CODEX_NATIVE_MODE_REPORT
|
|
3528
4177
|
};
|
|
3529
4178
|
},
|
|
3530
4179
|
startSession: async (input, signal) => {
|
|
@@ -3540,15 +4189,22 @@ var createCodexInstance = (config, host) => {
|
|
|
3540
4189
|
await stopBooked(existing);
|
|
3541
4190
|
threads.delete(input.threadId);
|
|
3542
4191
|
}
|
|
3543
|
-
const
|
|
4192
|
+
const nativeSubscription = input.launch.nativeSubscription === true;
|
|
4193
|
+
const layout = layoutFor(recipe, input.launch.homePath, nativeSubscription);
|
|
3544
4194
|
await materializeCodexShadowHome(layout, host.log);
|
|
4195
|
+
const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
|
|
3545
4196
|
const env = buildCodexSpawnEnv({
|
|
3546
4197
|
recipe,
|
|
3547
4198
|
layout,
|
|
3548
|
-
|
|
3549
|
-
|
|
4199
|
+
// MCP 那几个变量是宿主挑好的 server 带来的,与绑定同一层:实例表盖不得。
|
|
4200
|
+
launchEnv: { ...input.launch.processEnv, ...mcp.environment },
|
|
4201
|
+
crushApiKeys: nativeSubscription || shouldCrushApiKeys(recipe.authMode)
|
|
3550
4202
|
});
|
|
3551
|
-
const args = [
|
|
4203
|
+
const args = [
|
|
4204
|
+
...codexSpawnArgs(recipe, layout, nativeSubscription),
|
|
4205
|
+
...codexOverrideArgs(input.launch.launchArgOverrides),
|
|
4206
|
+
...mcp.args
|
|
4207
|
+
];
|
|
3552
4208
|
const handle = await host.process.spawn({
|
|
3553
4209
|
program: recipe.binaryPath,
|
|
3554
4210
|
args,
|
|
@@ -3668,6 +4324,11 @@ var createCodexInstance = (config, host) => {
|
|
|
3668
4324
|
recipe,
|
|
3669
4325
|
layout: layoutFor(recipe)
|
|
3670
4326
|
}),
|
|
4327
|
+
continuationKey: codexContinuationKey({
|
|
4328
|
+
homePath: recipe.homePath,
|
|
4329
|
+
instanceEnvironment: recipe.environment,
|
|
4330
|
+
inheritedEnvironment: process.env
|
|
4331
|
+
}),
|
|
3671
4332
|
sessions: {
|
|
3672
4333
|
scan: async (input) => {
|
|
3673
4334
|
if (recipe.binaryPath.length === 0) {
|
|
@@ -3677,7 +4338,6 @@ var createCodexInstance = (config, host) => {
|
|
|
3677
4338
|
const env = buildCodexSpawnEnv({
|
|
3678
4339
|
recipe,
|
|
3679
4340
|
layout,
|
|
3680
|
-
processEnv: {},
|
|
3681
4341
|
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
3682
4342
|
});
|
|
3683
4343
|
const args = [...codexSpawnArgs(recipe, layout)];
|
|
@@ -3715,7 +4375,6 @@ var createCodexInstance = (config, host) => {
|
|
|
3715
4375
|
const env = buildCodexSpawnEnv({
|
|
3716
4376
|
recipe,
|
|
3717
4377
|
layout,
|
|
3718
|
-
processEnv: {},
|
|
3719
4378
|
crushApiKeys: shouldCrushApiKeys(recipe.authMode)
|
|
3720
4379
|
});
|
|
3721
4380
|
const args = [...codexSpawnArgs(recipe, layout)];
|
|
@@ -3776,6 +4435,25 @@ var createCodexInstance = (config, host) => {
|
|
|
3776
4435
|
processEnv: process.env
|
|
3777
4436
|
})
|
|
3778
4437
|
},
|
|
4438
|
+
mcpOAuth: {
|
|
4439
|
+
start: async (input) => {
|
|
4440
|
+
const started = await startCodexMcpOAuth({
|
|
4441
|
+
host,
|
|
4442
|
+
recipe,
|
|
4443
|
+
layout: layoutFor(recipe),
|
|
4444
|
+
processEnv: process.env,
|
|
4445
|
+
server: input.server,
|
|
4446
|
+
cwd: input.cwd,
|
|
4447
|
+
...input.timeoutSecs === void 0 ? {} : { timeoutSecs: input.timeoutSecs }
|
|
4448
|
+
});
|
|
4449
|
+
trackHandle(started.handle, `${recipe.binaryPath} app-server (mcp oauth)`);
|
|
4450
|
+
return {
|
|
4451
|
+
authorizationUrl: started.authorizationUrl,
|
|
4452
|
+
completed: started.completed,
|
|
4453
|
+
close: started.close
|
|
4454
|
+
};
|
|
4455
|
+
}
|
|
4456
|
+
},
|
|
3779
4457
|
listLiveProcesses: async () => [...live.entries()].map(([pid, entry]) => ({
|
|
3780
4458
|
pid,
|
|
3781
4459
|
command: entry.command,
|