@ycodium-ai/agent-codex 0.2.3080 → 0.2.3086

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +820 -832
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -48,7 +48,7 @@ var CODEX_CARD = {
48
48
  // 三字段组合在 openThread 时定,切档要重开会话(与 runtimeModeSwitch 同为 next-turn)。
49
49
  nativeModeSwitch: true,
50
50
  contextCompaction: "native",
51
- // 子代理经 collab 通知报成 task.*(events.ts 的 mapCollabAgentEvent)。
51
+ // 子代理经 collab 通知报成 task.*(events/collabAgents.ts 的 mapCollabAgentEvent)。
52
52
  reportsSpawnedAgents: true,
53
53
  // 出处不是 provider 文档:这是产品此前对所有 provider 统一施加的单图上限(10 MiB),
54
54
  // codex 在这个上限下一直把图片以 data URL 内联。没有查到 Codex / OpenAI 对单图的明文
@@ -573,39 +573,15 @@ function codexExecLaunchArgs(launchArgs) {
573
573
  }
574
574
  return execArgs;
575
575
  }
576
- function hasConfiguredMcpServer(appServerArgs) {
577
- return appServerArgs.some((arg) => arg.includes("mcp_servers."));
578
- }
579
- var BUILTIN_MCP_SERVER_NAME = "ycodium";
580
- function codexBearerTokenEnvVar(name) {
581
- return name === BUILTIN_MCP_SERVER_NAME ? "YCODIUM_MCP_BEARER_TOKEN" : `YCODIUM_MCP_TOKEN_${name.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}`;
582
- }
583
- function codexMcpLaunchConfig(servers) {
584
- const args = [];
585
- const environment = {};
586
- const toml = (value) => JSON.stringify(value);
587
- for (const server of servers) {
588
- const key = `mcp_servers.${server.name}`;
589
- if (server.transport.type === "stdio") {
590
- args.push("-c", `${key}.command=${toml(server.transport.command)}`);
591
- const commandArgs = server.transport.args ?? [];
592
- if (commandArgs.length > 0) {
593
- args.push("-c", `${key}.args=[${commandArgs.map(toml).join(",")}]`);
594
- }
595
- for (const [name, value] of Object.entries(server.transport.env ?? {})) {
596
- args.push("-c", `${key}.env.${name}=${toml(value)}`);
597
- }
598
- continue;
599
- }
600
- args.push("-c", `${key}.url=${server.transport.url}`);
601
- const bearer = server.transport.headers?.["Authorization"]?.replace(/^Bearer\s+/, "");
602
- if (bearer) {
603
- const envVar = codexBearerTokenEnvVar(server.name);
604
- environment[envVar] = bearer;
605
- args.push("-c", `${key}.bearer_token_env_var=${toml(envVar)}`);
606
- }
607
- }
608
- return { args, environment };
576
+ function codexMcpThreadConfig(servers) {
577
+ return {
578
+ mcp_servers: Object.fromEntries(
579
+ servers.map(({ name, transport }) => [
580
+ name,
581
+ transport.type === "stdio" ? { command: transport.command, args: transport.args ?? [], env: transport.env ?? {} } : { url: transport.url, http_headers: transport.headers ?? {} }
582
+ ])
583
+ )
584
+ };
609
585
  }
610
586
  function modelIdFromSelection(selection) {
611
587
  if (typeof selection !== "object" || selection === null || Array.isArray(selection)) {
@@ -669,8 +645,7 @@ function buildCodexSpawnEnv(input) {
669
645
  return mergeLaunchEnv({
670
646
  accountHome: input.layout.effectiveHomePath !== void 0 ? { CODEX_HOME: input.layout.effectiveHomePath } : {},
671
647
  instanceTable,
672
- handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {},
673
- launch: input.launchEnv
648
+ handOverrides: input.crushApiKeys ? CRUSHED_API_KEYS : {}
674
649
  });
675
650
  }
676
651
  function nativeLoginSpawnEnv(processEnv, isolatedHome) {
@@ -689,13 +664,9 @@ async function spawnCodexAppServer(input) {
689
664
  const env = input.env ?? buildCodexSpawnEnv({
690
665
  recipe: input.recipe,
691
666
  layout: input.layout,
692
- ...input.launchEnv === void 0 ? {} : { launchEnv: input.launchEnv },
693
667
  crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
694
668
  });
695
- const args = input.args ?? [
696
- ...codexSpawnArgs(input.recipe, input.layout),
697
- ...input.extraArgs ?? []
698
- ];
669
+ const args = input.args ?? codexSpawnArgs(input.recipe, input.layout);
699
670
  const handle = await input.host.process.spawn({
700
671
  program: input.recipe.binaryPath,
701
672
  args,
@@ -1410,662 +1381,515 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
1410
1381
  <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>`;
1411
1382
  }
1412
1383
 
1413
- // src/events.ts
1384
+ // src/events/base.ts
1414
1385
  import * as NodeCrypto from "node:crypto";
1415
-
1416
- // src/codexArtifacts.ts
1417
- function imageGenerationArtifacts(input) {
1418
- if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
1419
- if (!isRecord3(input.params)) return [];
1420
- const item = isRecord3(input.params["item"]) ? input.params["item"] : input.params;
1421
- const savedPath = item["savedPath"];
1422
- if (typeof savedPath !== "string" || savedPath.trim().length === 0) return [];
1423
- return [{ path: savedPath, ...input.itemId ? { toolCallId: input.itemId } : {} }];
1386
+ var CODEX_PROVIDER = "codex";
1387
+ var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
1388
+ var eventSeq = 0;
1389
+ function nextEventId() {
1390
+ eventSeq += 1;
1391
+ return `${EVENT_ID_PREFIX}-${eventSeq}`;
1424
1392
  }
1425
-
1426
- // src/codexItemTypes.ts
1427
- function normalizeItemType(value) {
1428
- if (!value) return "item";
1429
- return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1393
+ function nowIso2() {
1394
+ return (/* @__PURE__ */ new Date()).toISOString();
1430
1395
  }
1431
- function toCanonicalItemType(raw) {
1432
- const type = normalizeItemType(raw);
1433
- if (type.includes("user")) return "user_message";
1434
- if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1435
- if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1436
- if (type.includes("plan") || type.includes("todo")) return "plan";
1437
- if (type.includes("command")) return "command_execution";
1438
- if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1439
- return "file_change";
1440
- }
1441
- if (type.includes("mcp")) return "mcp_tool_call";
1442
- if (type.includes("dynamic tool")) return "dynamic_tool_call";
1443
- if (type.includes("collab")) return "collab_agent_tool_call";
1444
- if (type.includes("web search")) return "web_search";
1445
- if (type.includes("image")) return "image_view";
1446
- if (type.includes("review entered")) return "review_entered";
1447
- if (type.includes("review exited")) return "review_exited";
1448
- if (type.includes("compact")) return "context_compaction";
1449
- if (type.includes("error")) return "error";
1450
- return "unknown";
1396
+ function eventRaw(source, method, payload) {
1397
+ return { source, method, payload };
1398
+ }
1399
+ function baseEvent(input) {
1400
+ return {
1401
+ eventId: nextEventId(),
1402
+ provider: CODEX_PROVIDER,
1403
+ threadId: input.threadId,
1404
+ createdAt: nowIso2(),
1405
+ type: input.type,
1406
+ payload: input.payload,
1407
+ ...input.providerInstanceId ? { providerInstanceId: input.providerInstanceId } : {},
1408
+ ...input.turnId ? { turnId: input.turnId } : {},
1409
+ ...input.itemId ? { itemId: input.itemId } : {},
1410
+ ...input.requestId ? { requestId: input.requestId } : {},
1411
+ ...input.method ? {
1412
+ raw: eventRaw(
1413
+ input.source ?? "codex.app-server.notification",
1414
+ input.method,
1415
+ input.payload
1416
+ )
1417
+ } : {}
1418
+ };
1451
1419
  }
1452
1420
 
1453
- // src/approvalMapping.ts
1454
- function fileChangeEntries(params) {
1455
- if (!isRecord3(params)) return [];
1421
+ // src/events/fields.ts
1422
+ function itemIdOf(params) {
1423
+ if (!isRecord3(params)) return void 0;
1456
1424
  const item = isRecord3(params["item"]) ? params["item"] : params;
1457
- const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
1458
- if (!isRecord3(changes) && !Array.isArray(changes)) return [];
1459
- const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
1460
- path,
1461
- ...isRecord3(change) ? change : {}
1462
- }));
1463
- const out = [];
1464
- for (const entry of entries) {
1465
- if (!isRecord3(entry)) continue;
1466
- const path = asString(entry["path"]) ?? asString(entry["file"]);
1467
- if (!path) continue;
1468
- out.push({
1469
- path,
1470
- kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1471
- movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1472
- diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1473
- content: asString(entry["content"])
1474
- });
1475
- }
1476
- return out;
1425
+ return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1477
1426
  }
1478
- var MAX_DESCRIBED_FILE_CHANGES = 20;
1479
- function describeFileChanges(params) {
1480
- const entries = fileChangeEntries(params).toSorted(
1481
- (left, right) => left.path.localeCompare(right.path)
1482
- );
1483
- if (entries.length === 0) return void 0;
1484
- const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1485
- (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1486
- );
1487
- const remaining = entries.length - lines.length;
1488
- return remaining > 0 ? `${lines.join("\n")}
1489
- +${remaining} more` : lines.join("\n");
1427
+ function itemStatusOf(params) {
1428
+ if (!isRecord3(params)) return void 0;
1429
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1430
+ return asString(item["status"]) ?? void 0;
1490
1431
  }
1491
- function toApplyPatchFileChanges(params) {
1492
- const out = [];
1493
- for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1494
- if (kind === "add") {
1495
- out.push({
1496
- path,
1497
- changeKind: "add",
1498
- ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1499
- });
1500
- } else if (kind === "delete") {
1501
- out.push({
1502
- path,
1503
- changeKind: "delete",
1504
- ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1505
- });
1506
- } else if (movePath) {
1507
- out.push({
1508
- path,
1509
- changeKind: "rename",
1510
- ...diff ? { content: { kind: "unified", diff } } : {}
1511
- });
1512
- } else {
1513
- out.push({
1514
- path,
1515
- changeKind: "modify",
1516
- ...diff ? { content: { kind: "unified", diff } } : {}
1517
- });
1518
- }
1519
- }
1520
- return out;
1432
+ function turnIdOf(params) {
1433
+ if (!isRecord3(params)) return void 0;
1434
+ return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
1521
1435
  }
1522
- function describeCommandApproval(params) {
1523
- const record = isRecord3(params) ? params : {};
1524
- const commandValue = record["command"];
1525
- const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
1526
- const cwd = asString(record["cwd"]);
1527
- const reason = trimText(record["reason"]);
1528
- const callId = asString(record["callId"]);
1529
- return {
1530
- command,
1531
- ...cwd ? { cwd } : {},
1532
- ...reason ? { reason } : {},
1533
- ...callId ? { callId } : {}
1534
- };
1436
+ function itemTitle(params) {
1437
+ if (!isRecord3(params)) return void 0;
1438
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1439
+ return trimText(item["title"]) ?? trimText(item["name"]) ?? trimText(item["command"]) ?? trimText(item["summary"]) ?? void 0;
1535
1440
  }
1536
- function describeFileChangeApproval(params) {
1537
- const record = isRecord3(params) ? params : {};
1538
- const reason = trimText(record["reason"]);
1539
- const grantRoot = asString(record["grantRoot"]);
1540
- const callId = asString(record["callId"]);
1541
- const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1542
- return {
1543
- ...reason ? { reason } : {},
1544
- ...detail ? { detail } : {},
1545
- ...grantRoot ? { grantRoot } : {},
1546
- ...callId ? { callId } : {},
1547
- fileChanges: toApplyPatchFileChanges(record)
1548
- };
1441
+ function itemDetail(params) {
1442
+ if (!isRecord3(params)) return void 0;
1443
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1444
+ const query = isRecord3(item["action"]) ? item["action"]["queries"] : item["query"];
1445
+ if (Array.isArray(query)) {
1446
+ const joined = query.filter((entry) => typeof entry === "string").join(", ");
1447
+ if (joined.length > 0) return joined;
1448
+ }
1449
+ return trimText(item["text"]) ?? trimText(item["summary"]) ?? trimText(item["path"]) ?? trimText(item["savedPath"]) ?? trimText(item["prompt"]) ?? (typeof query === "string" ? query : void 0);
1549
1450
  }
1550
- function describePermissionsApproval(params) {
1551
- const record = isRecord3(params) ? params : {};
1552
- const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1553
- const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1554
- const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1555
- const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1556
- const read = pathsOf(fileSystem["read"]);
1557
- const write = pathsOf(fileSystem["write"]);
1558
- const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1559
- for (const entry of entries) {
1560
- if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1561
- const target = entry["path"];
1562
- const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1563
- if (!path) continue;
1564
- if (entry["access"] === "read") read.push(path);
1565
- if (entry["access"] === "write") write.push(path);
1451
+ function toTurnStatus(status) {
1452
+ switch (status) {
1453
+ case "failed":
1454
+ return "failed";
1455
+ case "cancelled":
1456
+ case "canceled":
1457
+ return "cancelled";
1458
+ case "interrupted":
1459
+ return "interrupted";
1460
+ default:
1461
+ return "completed";
1566
1462
  }
1567
- const reason = trimText(record["reason"]);
1568
- return {
1569
- ...reason ? { reason } : {},
1570
- read: [...new Set(read)],
1571
- write: [...new Set(write)],
1572
- network: network["enabled"] === true
1573
- };
1574
1463
  }
1575
- function toPermissionsResponse(decision, params, persistent) {
1576
- if (decision !== "allow") return { permissions: {} };
1577
- const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
1578
- return {
1579
- permissions: {
1580
- ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1581
- ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1582
- },
1583
- scope: persistent ? "session" : "turn"
1584
- };
1464
+ function threadStateFromStatus(status) {
1465
+ const type = isRecord3(status) ? asString(status["type"]) : asString(status);
1466
+ if (type === "idle") return "idle";
1467
+ if (type === "systemError" || type === "error") return "error";
1468
+ return "active";
1585
1469
  }
1586
- function describeMcpElicitation(params) {
1587
- const record = isRecord3(params) ? params : {};
1588
- const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
1589
- const serverName = asString(record["serverName"]);
1590
- const message = trimText(record["message"]);
1591
- const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
1592
- const identity = appName ?? url ?? "unknown";
1593
- return {
1594
- ...appName ? { appName } : {},
1595
- ...serverName ? { serverName } : {},
1596
- ...message ? { message } : {},
1597
- ...url ? { url } : {},
1598
- identity,
1599
- mode: url ? "url" : "form",
1600
- ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
1601
- };
1602
- }
1603
- function persistFromMeta(meta) {
1604
- if (!isRecord3(meta)) return void 0;
1605
- const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
1606
- if (persist === "session" || persist === "acceptForSession") return "session";
1607
- if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
1608
- return "always";
1609
- }
1610
- return void 0;
1470
+ function codexCompactionId(threadId, turnId) {
1471
+ return `codex:${threadId}:${turnId ?? "session"}`;
1611
1472
  }
1612
- function toMcpElicitationResponse(decision, params) {
1613
- const described = describeMcpElicitation(params);
1614
- if (decision !== "allow" || described.mode === "url") {
1615
- return {
1616
- action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
1617
- };
1618
- }
1619
- const schema = isRecord3(described.schema) ? described.schema : void 0;
1620
- const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
1621
- const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
1622
- const content = {};
1623
- for (const [key, spec] of Object.entries(properties)) {
1624
- if (!isRecord3(spec)) continue;
1625
- if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
1626
- const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
1627
- const first = options[0];
1628
- content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
1629
- } else if (spec["type"] === "boolean") {
1630
- content[key] = true;
1631
- } else if (typeof spec["default"] !== "undefined") {
1632
- content[key] = spec["default"];
1633
- }
1634
- }
1635
- for (const key of required) {
1636
- if (typeof key === "string" && !(key in content)) {
1637
- return { action: "decline" };
1638
- }
1639
- }
1640
- const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
1473
+ function normalizeCodexTokenUsage(tokenUsage) {
1474
+ if (!isRecord3(tokenUsage)) return void 0;
1475
+ const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
1476
+ const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1477
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1478
+ const usedTokens = count(last?.["totalTokens"]);
1479
+ if (usedTokens === void 0 || usedTokens <= 0) return void 0;
1480
+ const totalProcessedTokens = count(total?.["totalTokens"]);
1481
+ const maxTokens = count(tokenUsage["modelContextWindow"]);
1482
+ const inputTokens = count(last?.["inputTokens"]);
1483
+ const cachedInputTokens = count(last?.["cachedInputTokens"]);
1484
+ const outputTokens = count(last?.["outputTokens"]);
1485
+ const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
1641
1486
  return {
1642
- action: "accept",
1643
- content,
1644
- ...persist ? { _meta: { persist } } : {}
1487
+ usedTokens,
1488
+ lastUsedTokens: usedTokens,
1489
+ ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
1490
+ ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
1491
+ ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
1492
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
1493
+ ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
1494
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
1495
+ compactsAutomatically: true
1645
1496
  };
1646
1497
  }
1498
+ function turnCompletedControlFlow(params) {
1499
+ const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
1500
+ const state = toTurnStatus(asString(turn["status"]) ?? void 0);
1501
+ const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
1502
+ return error ? { state, errorMessage: error } : { state };
1503
+ }
1647
1504
 
1648
- // src/usage/codexQuota.ts
1649
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1650
- var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1651
- var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1652
- var clampPercent = (value) => Math.min(100, Math.max(0, value));
1653
- var epochSecondsToIso = (value) => {
1654
- const seconds = asFiniteNumber(value);
1655
- if (seconds === null) return null;
1656
- const date = new Date(seconds * 1e3);
1657
- return Number.isNaN(date.getTime()) ? null : date.toISOString();
1658
- };
1659
- var SESSION_WINDOW_MINUTES = 5 * 60;
1660
- var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1661
- var formatWindowDuration = (minutes) => {
1662
- if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1663
- if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1664
- return `${String(minutes)}m`;
1665
- };
1666
- var classifyWindow = (minutes, slot) => {
1667
- if (minutes === SESSION_WINDOW_MINUTES) {
1668
- return { id: "session", label: { key: "usage.window.session" } };
1669
- }
1670
- if (minutes === WEEKLY_WINDOW_MINUTES) {
1671
- return { id: "weekly", label: { key: "usage.window.weekly" } };
1672
- }
1673
- if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
1674
- return {
1675
- id: `window-${String(minutes)}m`,
1676
- label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
1677
- };
1678
- };
1679
- var mapWindow = (raw, slot, describe) => {
1680
- const window = asRecord(raw);
1681
- const usedPercent = asFiniteNumber(window?.["usedPercent"]);
1682
- if (window === null || usedPercent === null) return null;
1683
- const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
1684
- const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
1685
- const { id, label } = describe(minutes, slot);
1686
- return {
1687
- id,
1688
- label,
1689
- usedPercent: clampPercent(usedPercent),
1690
- resetsAt: epochSecondsToIso(window["resetsAt"]),
1691
- ...minutes === null ? {} : { windowMs: minutes * 6e4 }
1692
- };
1693
- };
1694
- var PLAN_LABELS = {
1695
- free: "Free",
1696
- go: "Go",
1697
- plus: "Plus",
1698
- pro: "Pro",
1699
- prolite: "Pro Lite",
1700
- team: "Team",
1701
- business: "Business",
1702
- enterprise: "Enterprise",
1703
- edu: "Edu"
1704
- };
1705
- var planLabel = (planType) => {
1706
- const id = asNonEmptyString(planType);
1707
- if (id === null || id === "unknown") return void 0;
1708
- return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1709
- };
1710
- var creditBalance = (value) => {
1711
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
1712
- if (typeof value !== "string" || value.trim().length === 0) return null;
1713
- const numeric = Number(value.trim());
1714
- return Number.isFinite(numeric) ? numeric : null;
1715
- };
1716
- var MAIN_LIMIT_ID = "codex";
1717
- var CREDITS_BALANCE_ID = "credits";
1718
- var isMainCodexLimit = (snapshot) => {
1719
- const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
1720
- return limitId === null || limitId === MAIN_LIMIT_ID;
1721
- };
1722
- var mapCodexSnapshotWindows = (snapshot) => {
1723
- const bucket = asRecord(snapshot);
1724
- if (bucket === null || !isMainCodexLimit(bucket)) return [];
1725
- const windows = [];
1726
- const takenIds = /* @__PURE__ */ new Set();
1727
- for (const slot of ["primary", "secondary"]) {
1728
- const window = mapWindow(bucket[slot], slot, classifyWindow);
1729
- if (window === null) continue;
1730
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1731
- takenIds.add(id);
1732
- windows.push({ ...window, id, group: MAIN_LIMIT_ID });
1733
- }
1734
- return windows;
1735
- };
1736
- var mapCodexRateLimits = (response, accountKey) => {
1737
- const root = asRecord(response);
1738
- const legacy = asRecord(root?.["rateLimits"]);
1739
- const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
1740
- const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
1741
- const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
1742
- if (root === null || legacy === null && codexBucket === null) return null;
1743
- const windows = mapCodexSnapshotWindows(main);
1744
- const takenIds = new Set(windows.map((window) => window.id));
1745
- const push = (window, slot) => {
1746
- if (window === null) return;
1747
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1748
- takenIds.add(id);
1749
- windows.push({ ...window, id });
1505
+ // src/events/collabAgents.ts
1506
+ var COLLAB_CHILD_STATUSES = /* @__PURE__ */ new Set([
1507
+ "running",
1508
+ "idle",
1509
+ "failed",
1510
+ "interrupted",
1511
+ "cancelled"
1512
+ ]);
1513
+ function asCollabChildStatus(value) {
1514
+ return typeof value === "string" && COLLAB_CHILD_STATUSES.has(value) ? value : void 0;
1515
+ }
1516
+ function collabTurnEndStatus(turnStatus) {
1517
+ const state = toTurnStatus(turnStatus);
1518
+ return state === "completed" ? "idle" : state;
1519
+ }
1520
+ function pathLeaf(value) {
1521
+ if (!value) return void 0;
1522
+ const parts = value.split("/").filter((part) => part.length > 0);
1523
+ return parts.at(-1);
1524
+ }
1525
+ function mapCollabAgentEvent(input) {
1526
+ if (!isRecord3(input.params)) return [];
1527
+ const agentThreadId = asString(input.params["agentThreadId"]) ?? asString(input.params["threadId"]);
1528
+ if (!agentThreadId) return [];
1529
+ const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
1530
+ const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
1531
+ const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
1532
+ const knownName = nickname ?? pathLeaf(agentPath);
1533
+ const title = knownName ?? agentThreadId;
1534
+ const model = trimText(input.params["model"]);
1535
+ const effort = trimText(input.params["effort"]);
1536
+ const linkage = {
1537
+ role,
1538
+ ...knownName ? { title: knownName } : {},
1539
+ ...model ? { model } : {},
1540
+ ...effort ? { effort } : {},
1541
+ ...agentPath ? { agentPath } : {},
1542
+ timelineBypass: true,
1543
+ rosterKind: "subagent"
1750
1544
  };
1751
- const others = /* @__PURE__ */ new Map();
1752
- for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
1753
- const bucket = asRecord(rawBucket);
1754
- if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
1545
+ const threadId = input.rootThreadId;
1546
+ const method = input.method;
1547
+ if (method === "collabAgent/started" || method === "task.started") {
1548
+ return [
1549
+ baseEvent({
1550
+ type: "task.started",
1551
+ threadId,
1552
+ payload: { taskId: agentThreadId, ...linkage },
1553
+ method,
1554
+ providerInstanceId: input.providerInstanceId
1555
+ })
1556
+ ];
1755
1557
  }
1756
- const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
1757
- if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
1758
- if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
1558
+ if (method === "collabAgent/progress" || method === "task.progress") {
1559
+ const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
1560
+ return [
1561
+ baseEvent({
1562
+ type: "task.progress",
1563
+ threadId,
1564
+ // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
1565
+ payload: {
1566
+ taskId: agentThreadId,
1567
+ description: title,
1568
+ ...summary ? { summary } : {},
1569
+ ...linkage
1570
+ },
1571
+ method,
1572
+ providerInstanceId: input.providerInstanceId
1573
+ })
1574
+ ];
1759
1575
  }
1760
- for (const [limitId, bucket] of others) {
1761
- const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
1762
- for (const slot of ["primary", "secondary"]) {
1763
- const window = mapWindow(bucket[slot], slot, (minutes) => ({
1764
- id: `${limitId}:${slot}`,
1765
- label: minutes === null ? { text: name } : {
1766
- key: "usage.window.named",
1767
- values: { name, duration: formatWindowDuration(minutes) }
1768
- }
1769
- }));
1770
- push(window === null ? null : { ...window, group: limitId }, slot);
1771
- }
1576
+ if (method === "collabAgent/completed" || method === "task.completed") {
1577
+ const status = collabTurnEndStatus(asString(input.params["turnStatus"]) ?? void 0);
1578
+ return [
1579
+ baseEvent({
1580
+ type: "task.updated",
1581
+ threadId,
1582
+ payload: { taskId: agentThreadId, status, ...linkage },
1583
+ method,
1584
+ providerInstanceId: input.providerInstanceId
1585
+ })
1586
+ ];
1772
1587
  }
1773
- const balances = [];
1774
- const credits = asRecord(main?.["credits"]);
1775
- const balance = creditBalance(credits?.["balance"]);
1776
- if (credits !== null && credits["unlimited"] !== true && balance !== null) {
1777
- if (credits["hasCredits"] === true || balance > 0) {
1778
- balances.push({
1779
- id: CREDITS_BALANCE_ID,
1780
- label: { key: "usage.balance.credits" },
1781
- remaining: balance,
1782
- used: null,
1783
- limit: null,
1784
- unit: { kind: "credits" },
1785
- resetsAt: null
1786
- });
1787
- }
1588
+ if (method === "collabAgent/statusChanged") {
1589
+ const raw = asString(input.params["status"]);
1590
+ const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
1591
+ return [
1592
+ baseEvent({
1593
+ type: "task.updated",
1594
+ threadId,
1595
+ payload: { taskId: agentThreadId, status, ...linkage },
1596
+ method,
1597
+ providerInstanceId: input.providerInstanceId
1598
+ })
1599
+ ];
1788
1600
  }
1789
- const plan = planLabel((main ?? legacy)?.["planType"]);
1790
- const resetSummary = asRecord(root["rateLimitResetCredits"]);
1791
- const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
1792
- return {
1793
- status: "available",
1794
- ...plan === void 0 ? {} : { plan },
1795
- windows,
1796
- balances,
1797
- accountKey,
1798
- // 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
1799
- ...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
1800
- };
1801
- };
1802
- var codexQuotaFromRateLimits = (snapshot) => {
1803
- const bucket = asRecord(snapshot);
1804
- if (bucket === null) return null;
1805
- const reading = mapCodexRateLimits({ rateLimits: snapshot }, "");
1806
- if (reading === null || reading.status !== "available") return null;
1807
- const main = isMainCodexLimit(bucket);
1808
- const group = main ? MAIN_LIMIT_ID : asNonEmptyString(bucket["limitId"]);
1809
- const replaces = {
1810
- windowGroups: group === null ? [] : [group],
1811
- balanceIds: main ? [CREDITS_BALANCE_ID] : []
1812
- };
1813
- return {
1814
- windows: reading.windows,
1815
- balances: reading.balances,
1816
- ...reading.plan === void 0 ? {} : { plan: reading.plan },
1817
- replaces
1818
- };
1819
- };
1820
- var classifyRateLimitsError = (message) => {
1821
- const text = message.toLowerCase();
1822
- if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
1823
- if (text.includes("authentication required")) return "missingCredentials";
1824
- if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
1825
- if (/\b429\b|too many requests/.test(text)) return "rateLimited";
1826
- return "unreachable";
1827
- };
1828
- var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
1829
- var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
1830
- var isOfficialUrl = (value) => {
1831
- const parsed = URL.parse(value.trim());
1832
- return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
1833
- };
1834
- var readTomlScalar = (raw) => {
1835
- const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
1836
- if (quoted?.[2] !== void 0) return quoted[2];
1837
- const [beforeComment = ""] = raw.split("#");
1838
- return beforeComment.trim();
1839
- };
1840
- var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
1841
- var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
1842
- var BASE_URL_KEYS = /* @__PURE__ */ new Set([
1843
- "chatgpt_base_url",
1844
- "openai_base_url",
1845
- "base_url"
1846
- ]);
1847
- var codexConfigSignal = (contents, options) => {
1848
- let table = null;
1849
- let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
1850
- const providerBaseUrls = /* @__PURE__ */ new Map();
1851
- for (const rawLine of contents.split("\n")) {
1852
- const line = rawLine.trim();
1853
- if (line.length === 0 || line.startsWith("#")) continue;
1854
- const header = TABLE_HEADER.exec(line);
1855
- if (header !== null) {
1856
- table = header[1] ?? "";
1857
- if (table === "model_providers") return "config.toml:model_providers";
1858
- continue;
1859
- }
1860
- const assignment = KEY_VALUE.exec(line);
1861
- if (assignment === null) continue;
1862
- const key = assignment[1] ?? "";
1863
- const value = readTomlScalar(assignment[2] ?? "");
1864
- if (table === null) {
1865
- if (key === "model_provider") {
1866
- selectedProvider = value;
1867
- } else if (key === "profile" || key.startsWith("model_providers")) {
1868
- return `config.toml:${key.split(".")[0] ?? key}`;
1869
- } else if (BASE_URL_KEYS.has(key) && !isOfficialUrl(value)) {
1870
- return `config.toml:${key}`;
1871
- }
1872
- continue;
1873
- }
1874
- if (table.startsWith("model_providers.") && key === "base_url") {
1875
- providerBaseUrls.set(
1876
- table.slice("model_providers.".length).replace(/^["']|["']$/g, ""),
1877
- value
1878
- );
1879
- }
1601
+ if (method === "collabAgent/metadataUpdated") {
1602
+ return [
1603
+ baseEvent({
1604
+ type: "task.updated",
1605
+ threadId,
1606
+ payload: { taskId: agentThreadId, ...linkage },
1607
+ method,
1608
+ providerInstanceId: input.providerInstanceId
1609
+ })
1610
+ ];
1880
1611
  }
1881
- const selected = options.providerOverridden ? CODEX_DEFAULT_MODEL_PROVIDER : selectedProvider;
1882
- if (selected === CODEX_DEFAULT_MODEL_PROVIDER) return null;
1883
- const baseUrl = providerBaseUrls.get(selected);
1884
- if (baseUrl === void 0 || !isOfficialUrl(baseUrl)) return "config.toml:model_provider";
1885
- return null;
1886
- };
1887
- var LAUNCH_ARG_MARKERS = [
1888
- "model_provider",
1889
- "chatgpt_base_url",
1890
- "openai_base_url",
1891
- "base_url",
1892
- "--profile"
1893
- ];
1894
- var codexLaunchArgsSignal = (launchArgs) => {
1895
- for (const marker of LAUNCH_ARG_MARKERS) {
1896
- if (launchArgs.includes(marker)) return `launchArgs:${marker}`;
1612
+ if (method === "collabAgent/metadata") {
1613
+ const status = asCollabChildStatus(input.params["status"]);
1614
+ if (status === void 0) return [];
1615
+ return [
1616
+ baseEvent({
1617
+ type: "task.updated",
1618
+ threadId,
1619
+ payload: { taskId: agentThreadId, status, ...linkage },
1620
+ method,
1621
+ providerInstanceId: input.providerInstanceId
1622
+ })
1623
+ ];
1897
1624
  }
1898
- return null;
1899
- };
1900
-
1901
- // src/usageLimit.ts
1902
- var asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1903
- var mergeCodexRateLimits = (previous, update) => {
1904
- const next = asRecord2(update);
1905
- if (next === void 0 || !isMainCodexLimit(next)) return previous;
1906
- if (previous === void 0) return next;
1907
- const merged = { ...previous };
1908
- for (const key of ["limitId", "planType", "rateLimitReachedType", "primary", "secondary"]) {
1909
- if (next[key] !== void 0) merged[key] = next[key];
1625
+ if (method === "collabAgent/tokenUsage") {
1626
+ const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
1627
+ const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
1628
+ const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
1629
+ const totalTokens = total ? count(total["totalTokens"]) : void 0;
1630
+ if (total === void 0 || totalTokens === void 0) return [];
1631
+ const inputTokens = count(total["inputTokens"]);
1632
+ const cachedInputTokens = count(total["cachedInputTokens"]);
1633
+ const outputTokens = count(total["outputTokens"]);
1634
+ const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
1635
+ const typedUsage = {
1636
+ totalTokens,
1637
+ ...inputTokens !== void 0 ? { inputTokens } : {},
1638
+ ...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
1639
+ ...outputTokens !== void 0 ? { outputTokens } : {},
1640
+ ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
1641
+ };
1642
+ return [
1643
+ baseEvent({
1644
+ type: "task.progress",
1645
+ threadId,
1646
+ payload: {
1647
+ taskId: agentThreadId,
1648
+ description: title,
1649
+ typedUsage,
1650
+ ...linkage
1651
+ },
1652
+ method,
1653
+ providerInstanceId: input.providerInstanceId
1654
+ })
1655
+ ];
1910
1656
  }
1911
- return merged;
1912
- };
1913
- var formatUsageLimitWait = (waitMs) => {
1914
- const totalMinutes = Math.max(1, Math.ceil(waitMs / 6e4));
1915
- const days = Math.floor(totalMinutes / (24 * 60));
1916
- const hours = Math.floor(totalMinutes % (24 * 60) / 60);
1917
- const minutes = totalMinutes % 60;
1918
- if (days > 0) return hours === 0 ? `${String(days)}d` : `${String(days)}d ${String(hours)}h`;
1919
- if (hours === 0) return `${String(totalMinutes)}m`;
1920
- return minutes === 0 ? `${String(hours)}h` : `${String(hours)}h ${String(minutes)}m`;
1921
- };
1922
- var windowName = (window) => {
1923
- if (window.id === "session") return "5-hour";
1924
- if (window.id === "weekly") return "weekly";
1925
- return window.windowMs === void 0 ? "usage" : formatWindowDuration(window.windowMs / 6e4);
1926
- };
1927
- var nextStep = (rateLimitReachedType) => {
1928
- switch (rateLimitReachedType) {
1929
- case "workspace_owner_credits_depleted":
1930
- case "workspace_member_credits_depleted":
1931
- return " The workspace has no credits left to continue sooner: ask the workspace owner to add credits, or send the message again once the limit resets.";
1932
- case "workspace_owner_usage_limit_reached":
1933
- case "workspace_member_usage_limit_reached":
1934
- return " The workspace spend limit is reached: ask the workspace owner to raise it, or send the message again once the limit resets.";
1935
- default:
1936
- return void 0;
1657
+ return [];
1658
+ }
1659
+
1660
+ // src/approvalMapping.ts
1661
+ function fileChangeEntries(params) {
1662
+ if (!isRecord3(params)) return [];
1663
+ const item = isRecord3(params["item"]) ? params["item"] : params;
1664
+ const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
1665
+ if (!isRecord3(changes) && !Array.isArray(changes)) return [];
1666
+ const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
1667
+ path,
1668
+ ...isRecord3(change) ? change : {}
1669
+ }));
1670
+ const out = [];
1671
+ for (const entry of entries) {
1672
+ if (!isRecord3(entry)) continue;
1673
+ const path = asString(entry["path"]) ?? asString(entry["file"]);
1674
+ if (!path) continue;
1675
+ out.push({
1676
+ path,
1677
+ kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1678
+ movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1679
+ diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1680
+ content: asString(entry["content"])
1681
+ });
1937
1682
  }
1938
- };
1939
- var codexUsageLimitMessage = (snapshot, atIso, providerMessage) => {
1940
- const atMs = Date.parse(atIso);
1941
- let name;
1942
- let waitMs = 0;
1943
- let latestResetMs = Number.NEGATIVE_INFINITY;
1944
- if (snapshot !== void 0 && Number.isFinite(atMs)) {
1945
- for (const window of mapCodexSnapshotWindows(snapshot)) {
1946
- if (window.usedPercent < 100 || window.resetsAt === null) continue;
1947
- const resetMs = Date.parse(window.resetsAt);
1948
- if (!Number.isFinite(resetMs) || resetMs <= atMs || resetMs <= latestResetMs) continue;
1949
- latestResetMs = resetMs;
1950
- name = windowName(window);
1951
- waitMs = resetMs - atMs;
1683
+ return out;
1684
+ }
1685
+ var MAX_DESCRIBED_FILE_CHANGES = 20;
1686
+ function describeFileChanges(params) {
1687
+ const entries = fileChangeEntries(params).toSorted(
1688
+ (left, right) => left.path.localeCompare(right.path)
1689
+ );
1690
+ if (entries.length === 0) return void 0;
1691
+ const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1692
+ (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1693
+ );
1694
+ const remaining = entries.length - lines.length;
1695
+ return remaining > 0 ? `${lines.join("\n")}
1696
+ +${remaining} more` : lines.join("\n");
1697
+ }
1698
+ function toApplyPatchFileChanges(params) {
1699
+ const out = [];
1700
+ for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1701
+ if (kind === "add") {
1702
+ out.push({
1703
+ path,
1704
+ changeKind: "add",
1705
+ ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1706
+ });
1707
+ } else if (kind === "delete") {
1708
+ out.push({
1709
+ path,
1710
+ changeKind: "delete",
1711
+ ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1712
+ });
1713
+ } else if (movePath) {
1714
+ out.push({
1715
+ path,
1716
+ changeKind: "rename",
1717
+ ...diff ? { content: { kind: "unified", diff } } : {}
1718
+ });
1719
+ } else {
1720
+ out.push({
1721
+ path,
1722
+ changeKind: "modify",
1723
+ ...diff ? { content: { kind: "unified", diff } } : {}
1724
+ });
1952
1725
  }
1953
1726
  }
1954
- const step = nextStep(snapshot?.["rateLimitReachedType"]);
1955
- if (name === void 0) {
1956
- return `${providerMessage ?? "Codex usage limit reached."}${step ?? ""}`;
1957
- }
1958
- return `Codex usage limit reached. The ${name} limit resets in ${formatUsageLimitWait(waitMs)}.${step ?? " Send the message again once the limit resets."}`;
1959
- };
1960
- var isUsageLimitExceeded = (error) => asRecord2(error)?.["codexErrorInfo"] === "usageLimitExceeded";
1961
-
1962
- // src/events.ts
1963
- var CODEX_PROVIDER = "codex";
1964
- var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
1965
- var eventSeq = 0;
1966
- function nextEventId() {
1967
- eventSeq += 1;
1968
- return `${EVENT_ID_PREFIX}-${eventSeq}`;
1727
+ return out;
1969
1728
  }
1970
- function nowIso2() {
1971
- return (/* @__PURE__ */ new Date()).toISOString();
1729
+ function describeCommandApproval(params) {
1730
+ const record = isRecord3(params) ? params : {};
1731
+ const commandValue = record["command"];
1732
+ const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
1733
+ const cwd = asString(record["cwd"]);
1734
+ const reason = trimText(record["reason"]);
1735
+ const callId = asString(record["callId"]);
1736
+ return {
1737
+ command,
1738
+ ...cwd ? { cwd } : {},
1739
+ ...reason ? { reason } : {},
1740
+ ...callId ? { callId } : {}
1741
+ };
1972
1742
  }
1973
- function itemIdOf(params) {
1974
- if (!isRecord3(params)) return void 0;
1975
- const item = isRecord3(params["item"]) ? params["item"] : params;
1976
- return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1743
+ function describeFileChangeApproval(params) {
1744
+ const record = isRecord3(params) ? params : {};
1745
+ const reason = trimText(record["reason"]);
1746
+ const grantRoot = asString(record["grantRoot"]);
1747
+ const callId = asString(record["callId"]);
1748
+ const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1749
+ return {
1750
+ ...reason ? { reason } : {},
1751
+ ...detail ? { detail } : {},
1752
+ ...grantRoot ? { grantRoot } : {},
1753
+ ...callId ? { callId } : {},
1754
+ fileChanges: toApplyPatchFileChanges(record)
1755
+ };
1977
1756
  }
1978
- function itemStatusOf(params) {
1979
- if (!isRecord3(params)) return void 0;
1980
- const item = isRecord3(params["item"]) ? params["item"] : params;
1981
- return asString(item["status"]) ?? void 0;
1757
+ function describePermissionsApproval(params) {
1758
+ const record = isRecord3(params) ? params : {};
1759
+ const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1760
+ const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1761
+ const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1762
+ const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1763
+ const read = pathsOf(fileSystem["read"]);
1764
+ const write = pathsOf(fileSystem["write"]);
1765
+ const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1766
+ for (const entry of entries) {
1767
+ if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1768
+ const target = entry["path"];
1769
+ const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1770
+ if (!path) continue;
1771
+ if (entry["access"] === "read") read.push(path);
1772
+ if (entry["access"] === "write") write.push(path);
1773
+ }
1774
+ const reason = trimText(record["reason"]);
1775
+ return {
1776
+ ...reason ? { reason } : {},
1777
+ read: [...new Set(read)],
1778
+ write: [...new Set(write)],
1779
+ network: network["enabled"] === true
1780
+ };
1982
1781
  }
1983
- function turnIdOf(params) {
1984
- if (!isRecord3(params)) return void 0;
1985
- return asString(params["turnId"]) ?? (isRecord3(params["turn"]) ? asString(params["turn"]["id"]) : null) ?? void 0;
1782
+ function toPermissionsResponse(decision, params, persistent) {
1783
+ if (decision !== "allow") return { permissions: {} };
1784
+ const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
1785
+ return {
1786
+ permissions: {
1787
+ ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1788
+ ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1789
+ },
1790
+ scope: persistent ? "session" : "turn"
1791
+ };
1986
1792
  }
1987
- function itemTitle(params) {
1988
- if (!isRecord3(params)) return void 0;
1989
- const item = isRecord3(params["item"]) ? params["item"] : params;
1990
- return trimText(item["title"]) ?? trimText(item["name"]) ?? trimText(item["command"]) ?? trimText(item["summary"]) ?? void 0;
1793
+ function describeMcpElicitation(params) {
1794
+ const record = isRecord3(params) ? params : {};
1795
+ const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
1796
+ const serverName = asString(record["serverName"]);
1797
+ const message = trimText(record["message"]);
1798
+ const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
1799
+ const identity = appName ?? url ?? "unknown";
1800
+ return {
1801
+ ...appName ? { appName } : {},
1802
+ ...serverName ? { serverName } : {},
1803
+ ...message ? { message } : {},
1804
+ ...url ? { url } : {},
1805
+ identity,
1806
+ mode: url ? "url" : "form",
1807
+ ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
1808
+ };
1991
1809
  }
1992
- function itemDetail(params) {
1993
- if (!isRecord3(params)) return void 0;
1994
- const item = isRecord3(params["item"]) ? params["item"] : params;
1995
- const query = isRecord3(item["action"]) ? item["action"]["queries"] : item["query"];
1996
- if (Array.isArray(query)) {
1997
- const joined = query.filter((entry) => typeof entry === "string").join(", ");
1998
- if (joined.length > 0) return joined;
1810
+ function persistFromMeta(meta) {
1811
+ if (!isRecord3(meta)) return void 0;
1812
+ const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
1813
+ if (persist === "session" || persist === "acceptForSession") return "session";
1814
+ if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
1815
+ return "always";
1999
1816
  }
2000
- return trimText(item["text"]) ?? trimText(item["summary"]) ?? trimText(item["path"]) ?? trimText(item["savedPath"]) ?? trimText(item["prompt"]) ?? (typeof query === "string" ? query : void 0);
1817
+ return void 0;
2001
1818
  }
2002
- function toTurnStatus(status) {
2003
- switch (status) {
2004
- case "failed":
2005
- return "failed";
2006
- case "cancelled":
2007
- case "canceled":
2008
- return "cancelled";
2009
- case "interrupted":
2010
- return "interrupted";
2011
- default:
2012
- return "completed";
1819
+ function toMcpElicitationResponse(decision, params) {
1820
+ const described = describeMcpElicitation(params);
1821
+ if (decision !== "allow" || described.mode === "url") {
1822
+ return {
1823
+ action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
1824
+ };
2013
1825
  }
2014
- }
2015
- var COLLAB_CHILD_STATUSES = /* @__PURE__ */ new Set([
2016
- "running",
2017
- "idle",
2018
- "failed",
2019
- "interrupted",
2020
- "cancelled"
2021
- ]);
2022
- function asCollabChildStatus(value) {
2023
- return typeof value === "string" && COLLAB_CHILD_STATUSES.has(value) ? value : void 0;
2024
- }
2025
- function collabTurnEndStatus(turnStatus) {
2026
- const state = toTurnStatus(turnStatus);
2027
- return state === "completed" ? "idle" : state;
2028
- }
2029
- function threadStateFromStatus(status) {
2030
- const type = isRecord3(status) ? asString(status["type"]) : asString(status);
2031
- if (type === "idle") return "idle";
2032
- if (type === "systemError" || type === "error") return "error";
2033
- return "active";
2034
- }
2035
- function codexCompactionId(threadId, turnId) {
2036
- return `codex:${threadId}:${turnId ?? "session"}`;
2037
- }
2038
- function normalizeCodexTokenUsage(tokenUsage) {
2039
- if (!isRecord3(tokenUsage)) return void 0;
2040
- const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
2041
- const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2042
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2043
- const usedTokens = count(last?.["totalTokens"]);
2044
- if (usedTokens === void 0 || usedTokens <= 0) return void 0;
2045
- const totalProcessedTokens = count(total?.["totalTokens"]);
2046
- const maxTokens = count(tokenUsage["modelContextWindow"]);
2047
- const inputTokens = count(last?.["inputTokens"]);
2048
- const cachedInputTokens = count(last?.["cachedInputTokens"]);
2049
- const outputTokens = count(last?.["outputTokens"]);
2050
- const reasoningOutputTokens = count(last?.["reasoningOutputTokens"]);
1826
+ const schema = isRecord3(described.schema) ? described.schema : void 0;
1827
+ const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
1828
+ const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
1829
+ const content = {};
1830
+ for (const [key, spec] of Object.entries(properties)) {
1831
+ if (!isRecord3(spec)) continue;
1832
+ if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
1833
+ const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
1834
+ const first = options[0];
1835
+ content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
1836
+ } else if (spec["type"] === "boolean") {
1837
+ content[key] = true;
1838
+ } else if (typeof spec["default"] !== "undefined") {
1839
+ content[key] = spec["default"];
1840
+ }
1841
+ }
1842
+ for (const key of required) {
1843
+ if (typeof key === "string" && !(key in content)) {
1844
+ return { action: "decline" };
1845
+ }
1846
+ }
1847
+ const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
2051
1848
  return {
2052
- usedTokens,
2053
- lastUsedTokens: usedTokens,
2054
- ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
2055
- ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
2056
- ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
2057
- ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
2058
- ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
2059
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
2060
- compactsAutomatically: true
1849
+ action: "accept",
1850
+ content,
1851
+ ...persist ? { _meta: { persist } } : {}
2061
1852
  };
2062
1853
  }
2063
- function turnCompletedControlFlow(params) {
2064
- const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
2065
- const state = toTurnStatus(asString(turn["status"]) ?? void 0);
2066
- const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
2067
- return error ? { state, errorMessage: error } : { state };
1854
+
1855
+ // src/codexArtifacts.ts
1856
+ function imageGenerationArtifacts(input) {
1857
+ if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
1858
+ if (!isRecord3(input.params)) return [];
1859
+ const item = isRecord3(input.params["item"]) ? input.params["item"] : input.params;
1860
+ const savedPath = item["savedPath"];
1861
+ if (typeof savedPath !== "string" || savedPath.trim().length === 0) return [];
1862
+ return [{ path: savedPath, ...input.itemId ? { toolCallId: input.itemId } : {} }];
1863
+ }
1864
+
1865
+ // src/codexItemTypes.ts
1866
+ function normalizeItemType(value) {
1867
+ if (!value) return "item";
1868
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[._/-]/g, " ").replace(/\s+/g, " ").trim().toLowerCase();
1869
+ }
1870
+ function toCanonicalItemType(raw) {
1871
+ const type = normalizeItemType(raw);
1872
+ if (type.includes("user")) return "user_message";
1873
+ if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1874
+ if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1875
+ if (type.includes("plan") || type.includes("todo")) return "plan";
1876
+ if (type.includes("command")) return "command_execution";
1877
+ if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1878
+ return "file_change";
1879
+ }
1880
+ if (type.includes("mcp")) return "mcp_tool_call";
1881
+ if (type.includes("dynamic tool")) return "dynamic_tool_call";
1882
+ if (type.includes("collab")) return "collab_agent_tool_call";
1883
+ if (type.includes("web search")) return "web_search";
1884
+ if (type.includes("image")) return "image_view";
1885
+ if (type.includes("review entered")) return "review_entered";
1886
+ if (type.includes("review exited")) return "review_exited";
1887
+ if (type.includes("compact")) return "context_compaction";
1888
+ if (type.includes("error")) return "error";
1889
+ return "unknown";
2068
1890
  }
1891
+
1892
+ // src/events/userInput.ts
2069
1893
  function toUserInputQuestions(params) {
2070
1894
  if (!isRecord3(params)) return void 0;
2071
1895
  const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
@@ -2148,169 +1972,322 @@ function toCanonicalUserInputAnswers(answers) {
2148
1972
  ])
2149
1973
  );
2150
1974
  }
2151
- function pathLeaf(value) {
2152
- if (!value) return void 0;
2153
- const parts = value.split("/").filter((part) => part.length > 0);
2154
- return parts.at(-1);
2155
- }
2156
- function eventRaw(source, method, payload) {
2157
- return { source, method, payload };
2158
- }
2159
- function baseEvent(input) {
1975
+
1976
+ // src/usage/codexQuota.ts
1977
+ var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1978
+ var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1979
+ var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1980
+ var clampPercent = (value) => Math.min(100, Math.max(0, value));
1981
+ var epochSecondsToIso = (value) => {
1982
+ const seconds = asFiniteNumber(value);
1983
+ if (seconds === null) return null;
1984
+ const date = new Date(seconds * 1e3);
1985
+ return Number.isNaN(date.getTime()) ? null : date.toISOString();
1986
+ };
1987
+ var SESSION_WINDOW_MINUTES = 5 * 60;
1988
+ var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1989
+ var formatWindowDuration = (minutes) => {
1990
+ if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1991
+ if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1992
+ return `${String(minutes)}m`;
1993
+ };
1994
+ var classifyWindow = (minutes, slot) => {
1995
+ if (minutes === SESSION_WINDOW_MINUTES) {
1996
+ return { id: "session", label: { key: "usage.window.session" } };
1997
+ }
1998
+ if (minutes === WEEKLY_WINDOW_MINUTES) {
1999
+ return { id: "weekly", label: { key: "usage.window.weekly" } };
2000
+ }
2001
+ if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
2160
2002
  return {
2161
- eventId: nextEventId(),
2162
- provider: CODEX_PROVIDER,
2163
- threadId: input.threadId,
2164
- createdAt: nowIso2(),
2165
- type: input.type,
2166
- payload: input.payload,
2167
- ...input.providerInstanceId ? { providerInstanceId: input.providerInstanceId } : {},
2168
- ...input.turnId ? { turnId: input.turnId } : {},
2169
- ...input.itemId ? { itemId: input.itemId } : {},
2170
- ...input.requestId ? { requestId: input.requestId } : {},
2171
- ...input.method ? {
2172
- raw: eventRaw(
2173
- input.source ?? "codex.app-server.notification",
2174
- input.method,
2175
- input.payload
2176
- )
2177
- } : {}
2003
+ id: `window-${String(minutes)}m`,
2004
+ label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
2178
2005
  };
2179
- }
2180
- function mapCollabAgentEvent(input) {
2181
- if (!isRecord3(input.params)) return [];
2182
- const agentThreadId = asString(input.params["agentThreadId"]) ?? asString(input.params["threadId"]);
2183
- if (!agentThreadId) return [];
2184
- const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
2185
- const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
2186
- const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
2187
- const knownName = nickname ?? pathLeaf(agentPath);
2188
- const title = knownName ?? agentThreadId;
2189
- const model = trimText(input.params["model"]);
2190
- const effort = trimText(input.params["effort"]);
2191
- const linkage = {
2192
- role,
2193
- ...knownName ? { title: knownName } : {},
2194
- ...model ? { model } : {},
2195
- ...effort ? { effort } : {},
2196
- ...agentPath ? { agentPath } : {},
2197
- timelineBypass: true,
2198
- rosterKind: "subagent"
2006
+ };
2007
+ var mapWindow = (raw, slot, describe) => {
2008
+ const window = asRecord(raw);
2009
+ const usedPercent = asFiniteNumber(window?.["usedPercent"]);
2010
+ if (window === null || usedPercent === null) return null;
2011
+ const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
2012
+ const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
2013
+ const { id, label } = describe(minutes, slot);
2014
+ return {
2015
+ id,
2016
+ label,
2017
+ usedPercent: clampPercent(usedPercent),
2018
+ resetsAt: epochSecondsToIso(window["resetsAt"]),
2019
+ ...minutes === null ? {} : { windowMs: minutes * 6e4 }
2199
2020
  };
2200
- const threadId = input.rootThreadId;
2201
- const method = input.method;
2202
- if (method === "collabAgent/started" || method === "task.started") {
2203
- return [
2204
- baseEvent({
2205
- type: "task.started",
2206
- threadId,
2207
- payload: { taskId: agentThreadId, ...linkage },
2208
- method,
2209
- providerInstanceId: input.providerInstanceId
2210
- })
2211
- ];
2021
+ };
2022
+ var PLAN_LABELS = {
2023
+ free: "Free",
2024
+ go: "Go",
2025
+ plus: "Plus",
2026
+ pro: "Pro",
2027
+ prolite: "Pro Lite",
2028
+ team: "Team",
2029
+ business: "Business",
2030
+ enterprise: "Enterprise",
2031
+ edu: "Edu"
2032
+ };
2033
+ var planLabel = (planType) => {
2034
+ const id = asNonEmptyString(planType);
2035
+ if (id === null || id === "unknown") return void 0;
2036
+ return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
2037
+ };
2038
+ var creditBalance = (value) => {
2039
+ if (typeof value === "number") return Number.isFinite(value) ? value : null;
2040
+ if (typeof value !== "string" || value.trim().length === 0) return null;
2041
+ const numeric = Number(value.trim());
2042
+ return Number.isFinite(numeric) ? numeric : null;
2043
+ };
2044
+ var MAIN_LIMIT_ID = "codex";
2045
+ var CREDITS_BALANCE_ID = "credits";
2046
+ var isMainCodexLimit = (snapshot) => {
2047
+ const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
2048
+ return limitId === null || limitId === MAIN_LIMIT_ID;
2049
+ };
2050
+ var mapCodexSnapshotWindows = (snapshot) => {
2051
+ const bucket = asRecord(snapshot);
2052
+ if (bucket === null || !isMainCodexLimit(bucket)) return [];
2053
+ const windows = [];
2054
+ const takenIds = /* @__PURE__ */ new Set();
2055
+ for (const slot of ["primary", "secondary"]) {
2056
+ const window = mapWindow(bucket[slot], slot, classifyWindow);
2057
+ if (window === null) continue;
2058
+ const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
2059
+ takenIds.add(id);
2060
+ windows.push({ ...window, id, group: MAIN_LIMIT_ID });
2212
2061
  }
2213
- if (method === "collabAgent/progress" || method === "task.progress") {
2214
- const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
2215
- return [
2216
- baseEvent({
2217
- type: "task.progress",
2218
- threadId,
2219
- // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
2220
- payload: {
2221
- taskId: agentThreadId,
2222
- description: title,
2223
- ...summary ? { summary } : {},
2224
- ...linkage
2225
- },
2226
- method,
2227
- providerInstanceId: input.providerInstanceId
2228
- })
2229
- ];
2062
+ return windows;
2063
+ };
2064
+ var mapCodexRateLimits = (response, accountKey) => {
2065
+ const root = asRecord(response);
2066
+ const legacy = asRecord(root?.["rateLimits"]);
2067
+ const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
2068
+ const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
2069
+ const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
2070
+ if (root === null || legacy === null && codexBucket === null) return null;
2071
+ const windows = mapCodexSnapshotWindows(main);
2072
+ const takenIds = new Set(windows.map((window) => window.id));
2073
+ const push = (window, slot) => {
2074
+ if (window === null) return;
2075
+ const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
2076
+ takenIds.add(id);
2077
+ windows.push({ ...window, id });
2078
+ };
2079
+ const others = /* @__PURE__ */ new Map();
2080
+ for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
2081
+ const bucket = asRecord(rawBucket);
2082
+ if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
2083
+ }
2084
+ const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
2085
+ if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
2086
+ if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
2087
+ }
2088
+ for (const [limitId, bucket] of others) {
2089
+ const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
2090
+ for (const slot of ["primary", "secondary"]) {
2091
+ const window = mapWindow(bucket[slot], slot, (minutes) => ({
2092
+ id: `${limitId}:${slot}`,
2093
+ label: minutes === null ? { text: name } : {
2094
+ key: "usage.window.named",
2095
+ values: { name, duration: formatWindowDuration(minutes) }
2096
+ }
2097
+ }));
2098
+ push(window === null ? null : { ...window, group: limitId }, slot);
2099
+ }
2100
+ }
2101
+ const balances = [];
2102
+ const credits = asRecord(main?.["credits"]);
2103
+ const balance = creditBalance(credits?.["balance"]);
2104
+ if (credits !== null && credits["unlimited"] !== true && balance !== null) {
2105
+ if (credits["hasCredits"] === true || balance > 0) {
2106
+ balances.push({
2107
+ id: CREDITS_BALANCE_ID,
2108
+ label: { key: "usage.balance.credits" },
2109
+ remaining: balance,
2110
+ used: null,
2111
+ limit: null,
2112
+ unit: { kind: "credits" },
2113
+ resetsAt: null
2114
+ });
2115
+ }
2116
+ }
2117
+ const plan = planLabel((main ?? legacy)?.["planType"]);
2118
+ const resetSummary = asRecord(root["rateLimitResetCredits"]);
2119
+ const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
2120
+ return {
2121
+ status: "available",
2122
+ ...plan === void 0 ? {} : { plan },
2123
+ windows,
2124
+ balances,
2125
+ accountKey,
2126
+ // 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
2127
+ ...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
2128
+ };
2129
+ };
2130
+ var codexQuotaFromRateLimits = (snapshot) => {
2131
+ const bucket = asRecord(snapshot);
2132
+ if (bucket === null) return null;
2133
+ const reading = mapCodexRateLimits({ rateLimits: snapshot }, "");
2134
+ if (reading === null || reading.status !== "available") return null;
2135
+ const main = isMainCodexLimit(bucket);
2136
+ const group = main ? MAIN_LIMIT_ID : asNonEmptyString(bucket["limitId"]);
2137
+ const replaces = {
2138
+ windowGroups: group === null ? [] : [group],
2139
+ balanceIds: main ? [CREDITS_BALANCE_ID] : []
2140
+ };
2141
+ return {
2142
+ windows: reading.windows,
2143
+ balances: reading.balances,
2144
+ ...reading.plan === void 0 ? {} : { plan: reading.plan },
2145
+ replaces
2146
+ };
2147
+ };
2148
+ var classifyRateLimitsError = (message) => {
2149
+ const text = message.toLowerCase();
2150
+ if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
2151
+ if (text.includes("authentication required")) return "missingCredentials";
2152
+ if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
2153
+ if (/\b429\b|too many requests/.test(text)) return "rateLimited";
2154
+ return "unreachable";
2155
+ };
2156
+ var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
2157
+ var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
2158
+ var isOfficialUrl = (value) => {
2159
+ const parsed = URL.parse(value.trim());
2160
+ return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
2161
+ };
2162
+ var readTomlScalar = (raw) => {
2163
+ const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
2164
+ if (quoted?.[2] !== void 0) return quoted[2];
2165
+ const [beforeComment = ""] = raw.split("#");
2166
+ return beforeComment.trim();
2167
+ };
2168
+ var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
2169
+ var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
2170
+ var BASE_URL_KEYS = /* @__PURE__ */ new Set([
2171
+ "chatgpt_base_url",
2172
+ "openai_base_url",
2173
+ "base_url"
2174
+ ]);
2175
+ var codexConfigSignal = (contents, options) => {
2176
+ let table = null;
2177
+ let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
2178
+ const providerBaseUrls = /* @__PURE__ */ new Map();
2179
+ for (const rawLine of contents.split("\n")) {
2180
+ const line = rawLine.trim();
2181
+ if (line.length === 0 || line.startsWith("#")) continue;
2182
+ const header = TABLE_HEADER.exec(line);
2183
+ if (header !== null) {
2184
+ table = header[1] ?? "";
2185
+ if (table === "model_providers") return "config.toml:model_providers";
2186
+ continue;
2187
+ }
2188
+ const assignment = KEY_VALUE.exec(line);
2189
+ if (assignment === null) continue;
2190
+ const key = assignment[1] ?? "";
2191
+ const value = readTomlScalar(assignment[2] ?? "");
2192
+ if (table === null) {
2193
+ if (key === "model_provider") {
2194
+ selectedProvider = value;
2195
+ } else if (key === "profile" || key.startsWith("model_providers")) {
2196
+ return `config.toml:${key.split(".")[0] ?? key}`;
2197
+ } else if (BASE_URL_KEYS.has(key) && !isOfficialUrl(value)) {
2198
+ return `config.toml:${key}`;
2199
+ }
2200
+ continue;
2201
+ }
2202
+ if (table.startsWith("model_providers.") && key === "base_url") {
2203
+ providerBaseUrls.set(
2204
+ table.slice("model_providers.".length).replace(/^["']|["']$/g, ""),
2205
+ value
2206
+ );
2207
+ }
2230
2208
  }
2231
- if (method === "collabAgent/completed" || method === "task.completed") {
2232
- const status = collabTurnEndStatus(asString(input.params["turnStatus"]) ?? void 0);
2233
- return [
2234
- baseEvent({
2235
- type: "task.updated",
2236
- threadId,
2237
- payload: { taskId: agentThreadId, status, ...linkage },
2238
- method,
2239
- providerInstanceId: input.providerInstanceId
2240
- })
2241
- ];
2209
+ const selected = options.providerOverridden ? CODEX_DEFAULT_MODEL_PROVIDER : selectedProvider;
2210
+ if (selected === CODEX_DEFAULT_MODEL_PROVIDER) return null;
2211
+ const baseUrl = providerBaseUrls.get(selected);
2212
+ if (baseUrl === void 0 || !isOfficialUrl(baseUrl)) return "config.toml:model_provider";
2213
+ return null;
2214
+ };
2215
+ var LAUNCH_ARG_MARKERS = [
2216
+ "model_provider",
2217
+ "chatgpt_base_url",
2218
+ "openai_base_url",
2219
+ "base_url",
2220
+ "--profile"
2221
+ ];
2222
+ var codexLaunchArgsSignal = (launchArgs) => {
2223
+ for (const marker of LAUNCH_ARG_MARKERS) {
2224
+ if (launchArgs.includes(marker)) return `launchArgs:${marker}`;
2242
2225
  }
2243
- if (method === "collabAgent/statusChanged") {
2244
- const raw = asString(input.params["status"]);
2245
- const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
2246
- return [
2247
- baseEvent({
2248
- type: "task.updated",
2249
- threadId,
2250
- payload: { taskId: agentThreadId, status, ...linkage },
2251
- method,
2252
- providerInstanceId: input.providerInstanceId
2253
- })
2254
- ];
2226
+ return null;
2227
+ };
2228
+
2229
+ // src/usageLimit.ts
2230
+ var asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2231
+ var mergeCodexRateLimits = (previous, update) => {
2232
+ const next = asRecord2(update);
2233
+ if (next === void 0 || !isMainCodexLimit(next)) return previous;
2234
+ if (previous === void 0) return next;
2235
+ const merged = { ...previous };
2236
+ for (const key of ["limitId", "planType", "rateLimitReachedType", "primary", "secondary"]) {
2237
+ if (next[key] !== void 0) merged[key] = next[key];
2255
2238
  }
2256
- if (method === "collabAgent/metadataUpdated") {
2257
- return [
2258
- baseEvent({
2259
- type: "task.updated",
2260
- threadId,
2261
- payload: { taskId: agentThreadId, ...linkage },
2262
- method,
2263
- providerInstanceId: input.providerInstanceId
2264
- })
2265
- ];
2239
+ return merged;
2240
+ };
2241
+ var formatUsageLimitWait = (waitMs) => {
2242
+ const totalMinutes = Math.max(1, Math.ceil(waitMs / 6e4));
2243
+ const days = Math.floor(totalMinutes / (24 * 60));
2244
+ const hours = Math.floor(totalMinutes % (24 * 60) / 60);
2245
+ const minutes = totalMinutes % 60;
2246
+ if (days > 0) return hours === 0 ? `${String(days)}d` : `${String(days)}d ${String(hours)}h`;
2247
+ if (hours === 0) return `${String(totalMinutes)}m`;
2248
+ return minutes === 0 ? `${String(hours)}h` : `${String(hours)}h ${String(minutes)}m`;
2249
+ };
2250
+ var windowName = (window) => {
2251
+ if (window.id === "session") return "5-hour";
2252
+ if (window.id === "weekly") return "weekly";
2253
+ return window.windowMs === void 0 ? "usage" : formatWindowDuration(window.windowMs / 6e4);
2254
+ };
2255
+ var nextStep = (rateLimitReachedType) => {
2256
+ switch (rateLimitReachedType) {
2257
+ case "workspace_owner_credits_depleted":
2258
+ case "workspace_member_credits_depleted":
2259
+ return " The workspace has no credits left to continue sooner: ask the workspace owner to add credits, or send the message again once the limit resets.";
2260
+ case "workspace_owner_usage_limit_reached":
2261
+ case "workspace_member_usage_limit_reached":
2262
+ return " The workspace spend limit is reached: ask the workspace owner to raise it, or send the message again once the limit resets.";
2263
+ default:
2264
+ return void 0;
2266
2265
  }
2267
- if (method === "collabAgent/metadata") {
2268
- const status = asCollabChildStatus(input.params["status"]);
2269
- if (status === void 0) return [];
2270
- return [
2271
- baseEvent({
2272
- type: "task.updated",
2273
- threadId,
2274
- payload: { taskId: agentThreadId, status, ...linkage },
2275
- method,
2276
- providerInstanceId: input.providerInstanceId
2277
- })
2278
- ];
2266
+ };
2267
+ var codexUsageLimitMessage = (snapshot, atIso, providerMessage) => {
2268
+ const atMs = Date.parse(atIso);
2269
+ let name;
2270
+ let waitMs = 0;
2271
+ let latestResetMs = Number.NEGATIVE_INFINITY;
2272
+ if (snapshot !== void 0 && Number.isFinite(atMs)) {
2273
+ for (const window of mapCodexSnapshotWindows(snapshot)) {
2274
+ if (window.usedPercent < 100 || window.resetsAt === null) continue;
2275
+ const resetMs = Date.parse(window.resetsAt);
2276
+ if (!Number.isFinite(resetMs) || resetMs <= atMs || resetMs <= latestResetMs) continue;
2277
+ latestResetMs = resetMs;
2278
+ name = windowName(window);
2279
+ waitMs = resetMs - atMs;
2280
+ }
2279
2281
  }
2280
- if (method === "collabAgent/tokenUsage") {
2281
- const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
2282
- const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2283
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2284
- const totalTokens = total ? count(total["totalTokens"]) : void 0;
2285
- if (total === void 0 || totalTokens === void 0) return [];
2286
- const inputTokens = count(total["inputTokens"]);
2287
- const cachedInputTokens = count(total["cachedInputTokens"]);
2288
- const outputTokens = count(total["outputTokens"]);
2289
- const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
2290
- const typedUsage = {
2291
- totalTokens,
2292
- ...inputTokens !== void 0 ? { inputTokens } : {},
2293
- ...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
2294
- ...outputTokens !== void 0 ? { outputTokens } : {},
2295
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
2296
- };
2297
- return [
2298
- baseEvent({
2299
- type: "task.progress",
2300
- threadId,
2301
- payload: {
2302
- taskId: agentThreadId,
2303
- description: title,
2304
- typedUsage,
2305
- ...linkage
2306
- },
2307
- method,
2308
- providerInstanceId: input.providerInstanceId
2309
- })
2310
- ];
2282
+ const step = nextStep(snapshot?.["rateLimitReachedType"]);
2283
+ if (name === void 0) {
2284
+ return `${providerMessage ?? "Codex usage limit reached."}${step ?? ""}`;
2311
2285
  }
2312
- return [];
2313
- }
2286
+ return `Codex usage limit reached. The ${name} limit resets in ${formatUsageLimitWait(waitMs)}.${step ?? " Send the message again once the limit resets."}`;
2287
+ };
2288
+ var isUsageLimitExceeded = (error) => asRecord2(error)?.["codexErrorInfo"] === "usageLimitExceeded";
2289
+
2290
+ // src/events.ts
2314
2291
  function mapNotificationToEvents(input) {
2315
2292
  const { method, params, threadId, providerInstanceId, rateLimits } = input;
2316
2293
  if (method.startsWith("collabAgent/")) {
@@ -2880,6 +2857,8 @@ function mapNotificationToEvents(input) {
2880
2857
  return [];
2881
2858
  }
2882
2859
  }
2860
+
2861
+ // src/events/serverRequests.ts
2883
2862
  function toRequestTypeFromKind(kind) {
2884
2863
  switch (kind) {
2885
2864
  case "command":
@@ -3150,6 +3129,7 @@ var CodexSessionRuntime = class {
3150
3129
  /** 这个会话见过的主额度快照(按字段合并),额度用尽停轮时点名窗口用。 */
3151
3130
  rateLimits;
3152
3131
  appServerArgs;
3132
+ hasConfiguredMcpServer;
3153
3133
  host;
3154
3134
  attachments;
3155
3135
  rpc;
@@ -3192,6 +3172,7 @@ var CodexSessionRuntime = class {
3192
3172
  this.runtimeMode = input.runtimeMode;
3193
3173
  this.nativeModeId = input.nativeModeId;
3194
3174
  this.appServerArgs = appServerArgs;
3175
+ this.hasConfiguredMcpServer = (input.mcpServers?.length ?? 0) > 0;
3195
3176
  this.grants = grants ?? createApprovalGrantStore();
3196
3177
  this.declineRelay = declineRelay ?? createDeclineReasonRelay();
3197
3178
  const createdAt = nowIso();
@@ -3711,21 +3692,14 @@ var CodexSessionRuntime = class {
3711
3692
  }
3712
3693
  throw lastError instanceof Error ? lastError : new Error(String(lastError));
3713
3694
  }
3714
- threadConfigParams() {
3715
- const config = this.#threadConfig();
3716
- return {
3717
- approvalPolicy: config.approvalPolicy,
3718
- sandbox: config.sandbox,
3719
- approvalsReviewer: config.approvalsReviewer,
3720
- cwd: this.cwd
3721
- };
3722
- }
3723
3695
  async openThread(input) {
3724
3696
  const resume = input.discardResumeCursor === true ? void 0 : parseCodexResumeCursor(input.resumeCursor);
3725
3697
  const requestedModel = modelIdFromSelection(input.modelSelection);
3726
3698
  const startParams = {
3727
- ...this.threadConfigParams(),
3728
- ...requestedModel ? { model: requestedModel } : {}
3699
+ ...this.#threadConfig(),
3700
+ cwd: this.cwd,
3701
+ ...requestedModel ? { model: requestedModel } : {},
3702
+ ...this.hasConfiguredMcpServer ? { config: codexMcpThreadConfig(input.mcpServers ?? []) } : {}
3729
3703
  };
3730
3704
  const result = resume ? await this.withActiveWriterRecovery(
3731
3705
  () => this.rpc.request("thread/resume", { threadId: resume.threadId, ...startParams }, 0),
@@ -3762,7 +3736,7 @@ var CodexSessionRuntime = class {
3762
3736
  return mode;
3763
3737
  }
3764
3738
  developerInstructions(input, mode) {
3765
- const tools = hasConfiguredMcpServer(this.appServerArgs);
3739
+ const tools = this.hasConfiguredMcpServer;
3766
3740
  const model = this.model ?? DEFAULT_MODEL;
3767
3741
  const effort = modelSelectionStringOption(input.modelSelection, "reasoningEffort") ?? DEFAULT_TEXT_GENERATION_REASONING_EFFORT;
3768
3742
  return buildCodexDeveloperInstructions(mode, { model, reasoningEffort: effort }, tools, tools);
@@ -3799,7 +3773,7 @@ var CodexSessionRuntime = class {
3799
3773
  }
3800
3774
  async sendTurn(input) {
3801
3775
  if (!this.nativeThreadId) throw new Error("codex session has no native thread");
3802
- if (hasConfiguredMcpServer(this.appServerArgs)) {
3776
+ if (this.hasConfiguredMcpServer) {
3803
3777
  await this.rpc.request("config/mcpServer/reload", void 0, 0).catch((error) => {
3804
3778
  this.host.log.warn("refreshing the codex MCP tool list before a turn failed", {
3805
3779
  threadId: this.threadId,
@@ -4179,17 +4153,35 @@ async function readRolloutModel(input, rolloutPath, sameDirectory) {
4179
4153
  }
4180
4154
 
4181
4155
  // src/mcpOAuth.ts
4156
+ function codexMcpOAuthLaunchConfig(name, transport) {
4157
+ const key = `mcp_servers.${name}`;
4158
+ const args = ["-c", `${key}.url=${transport.url}`];
4159
+ const environment = {};
4160
+ const bearer = transport.headers?.["Authorization"]?.replace(/^Bearer\s+/, "");
4161
+ if (bearer) {
4162
+ const envVar = name === "ycodium" ? "YCODIUM_MCP_BEARER_TOKEN" : `YCODIUM_MCP_TOKEN_${name.toUpperCase().replaceAll(/[^A-Z0-9]/g, "_")}`;
4163
+ environment[envVar] = bearer;
4164
+ args.push("-c", `${key}.bearer_token_env_var=${JSON.stringify(envVar)}`);
4165
+ }
4166
+ return { args, environment };
4167
+ }
4182
4168
  async function startCodexMcpOAuth(input) {
4183
4169
  if (input.recipe.binaryPath.length === 0) {
4184
4170
  throw new Error("Codex program path is not configured");
4185
4171
  }
4186
- const mcp = codexMcpLaunchConfig([input.server]);
4187
- const env = buildCodexSpawnEnv({
4188
- recipe: input.recipe,
4189
- layout: input.layout,
4190
- launchEnv: { ...input.processEnv, ...mcp.environment },
4191
- crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
4192
- });
4172
+ if (input.server.transport.type === "stdio") {
4173
+ throw new Error(`MCP OAuth needs an http server: ${input.server.name}`);
4174
+ }
4175
+ const mcp = codexMcpOAuthLaunchConfig(input.server.name, input.server.transport);
4176
+ const env = {
4177
+ ...buildCodexSpawnEnv({
4178
+ recipe: input.recipe,
4179
+ layout: input.layout,
4180
+ crushApiKeys: shouldCrushApiKeys(input.recipe.authMode)
4181
+ }),
4182
+ ...input.processEnv,
4183
+ ...mcp.environment
4184
+ };
4193
4185
  const { handle } = await spawnCodexAppServer({
4194
4186
  host: input.host,
4195
4187
  recipe: input.recipe,
@@ -5906,15 +5898,11 @@ var createCodexInstance = (config, host, attachments, cache) => {
5906
5898
  threads.delete(input.threadId);
5907
5899
  }
5908
5900
  const layout = layoutFor(recipe);
5909
- const mcp = codexMcpLaunchConfig(input.mcpServers ?? []);
5910
5901
  const { handle, args } = await spawnCodexAppServer({
5911
5902
  host,
5912
5903
  recipe,
5913
5904
  layout,
5914
- cwd,
5915
- extraArgs: mcp.args,
5916
- // MCP 那几个变量是宿主挑好的 server 带来的,铺在最终层:实例表盖不得。
5917
- launchEnv: mcp.environment
5905
+ cwd
5918
5906
  });
5919
5907
  trackHandle(handle, [recipe.binaryPath, ...args].join(" "), input.threadId);
5920
5908
  let runtime;