@ycodium-ai/agent-codex 0.2.3082 → 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 +795 -787
  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 对单图的明文
@@ -1381,662 +1381,515 @@ function buildCodexDeveloperInstructions(interactionMode, runtime, browserToolsA
1381
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>`;
1382
1382
  }
1383
1383
 
1384
- // src/events.ts
1384
+ // src/events/base.ts
1385
1385
  import * as NodeCrypto from "node:crypto";
1386
-
1387
- // src/codexArtifacts.ts
1388
- function imageGenerationArtifacts(input) {
1389
- if (input.itemType !== "imageGeneration" || input.status !== "completed") return [];
1390
- if (!isRecord3(input.params)) return [];
1391
- const item = isRecord3(input.params["item"]) ? input.params["item"] : input.params;
1392
- const savedPath = item["savedPath"];
1393
- if (typeof savedPath !== "string" || savedPath.trim().length === 0) return [];
1394
- 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}`;
1395
1392
  }
1396
-
1397
- // src/codexItemTypes.ts
1398
- function normalizeItemType(value) {
1399
- if (!value) return "item";
1400
- 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();
1401
1395
  }
1402
- function toCanonicalItemType(raw) {
1403
- const type = normalizeItemType(raw);
1404
- if (type.includes("user")) return "user_message";
1405
- if (type.includes("agent message") || type.includes("assistant")) return "assistant_message";
1406
- if (type.includes("reasoning") || type.includes("thought")) return "reasoning";
1407
- if (type.includes("plan") || type.includes("todo")) return "plan";
1408
- if (type.includes("command")) return "command_execution";
1409
- if (type.includes("file change") || type.includes("patch") || type.includes("edit")) {
1410
- return "file_change";
1411
- }
1412
- if (type.includes("mcp")) return "mcp_tool_call";
1413
- if (type.includes("dynamic tool")) return "dynamic_tool_call";
1414
- if (type.includes("collab")) return "collab_agent_tool_call";
1415
- if (type.includes("web search")) return "web_search";
1416
- if (type.includes("image")) return "image_view";
1417
- if (type.includes("review entered")) return "review_entered";
1418
- if (type.includes("review exited")) return "review_exited";
1419
- if (type.includes("compact")) return "context_compaction";
1420
- if (type.includes("error")) return "error";
1421
- 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
+ };
1422
1419
  }
1423
1420
 
1424
- // src/approvalMapping.ts
1425
- function fileChangeEntries(params) {
1426
- if (!isRecord3(params)) return [];
1421
+ // src/events/fields.ts
1422
+ function itemIdOf(params) {
1423
+ if (!isRecord3(params)) return void 0;
1427
1424
  const item = isRecord3(params["item"]) ? params["item"] : params;
1428
- const changes = item["changes"] ?? item["fileChanges"] ?? params["fileChanges"];
1429
- if (!isRecord3(changes) && !Array.isArray(changes)) return [];
1430
- const entries = Array.isArray(changes) ? changes : Object.entries(changes).map(([path, change]) => ({
1431
- path,
1432
- ...isRecord3(change) ? change : {}
1433
- }));
1434
- const out = [];
1435
- for (const entry of entries) {
1436
- if (!isRecord3(entry)) continue;
1437
- const path = asString(entry["path"]) ?? asString(entry["file"]);
1438
- if (!path) continue;
1439
- out.push({
1440
- path,
1441
- kind: (asString(entry["type"]) ?? asString(entry["kind"]) ?? "update").toLowerCase(),
1442
- movePath: asString(entry["move_path"]) ?? asString(entry["movePath"]),
1443
- diff: asString(entry["unified_diff"]) ?? asString(entry["unifiedDiff"]),
1444
- content: asString(entry["content"])
1445
- });
1446
- }
1447
- return out;
1425
+ return asString(item["id"]) ?? asString(params["itemId"]) ?? void 0;
1448
1426
  }
1449
- var MAX_DESCRIBED_FILE_CHANGES = 20;
1450
- function describeFileChanges(params) {
1451
- const entries = fileChangeEntries(params).toSorted(
1452
- (left, right) => left.path.localeCompare(right.path)
1453
- );
1454
- if (entries.length === 0) return void 0;
1455
- const lines = entries.slice(0, MAX_DESCRIBED_FILE_CHANGES).map(
1456
- (entry) => entry.movePath ? `${entry.kind} ${entry.path} -> ${entry.movePath}` : `${entry.kind} ${entry.path}`
1457
- );
1458
- const remaining = entries.length - lines.length;
1459
- return remaining > 0 ? `${lines.join("\n")}
1460
- +${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;
1461
1431
  }
1462
- function toApplyPatchFileChanges(params) {
1463
- const out = [];
1464
- for (const { path, kind, movePath, diff, content } of fileChangeEntries(params)) {
1465
- if (kind === "add") {
1466
- out.push({
1467
- path,
1468
- changeKind: "add",
1469
- ...content !== void 0 ? { content: { kind: "replace", newText: content } } : {}
1470
- });
1471
- } else if (kind === "delete") {
1472
- out.push({
1473
- path,
1474
- changeKind: "delete",
1475
- ...content !== void 0 ? { content: { kind: "replace", oldText: content, newText: "" } } : {}
1476
- });
1477
- } else if (movePath) {
1478
- out.push({
1479
- path,
1480
- changeKind: "rename",
1481
- ...diff ? { content: { kind: "unified", diff } } : {}
1482
- });
1483
- } else {
1484
- out.push({
1485
- path,
1486
- changeKind: "modify",
1487
- ...diff ? { content: { kind: "unified", diff } } : {}
1488
- });
1489
- }
1490
- }
1491
- 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;
1492
1435
  }
1493
- function describeCommandApproval(params) {
1494
- const record = isRecord3(params) ? params : {};
1495
- const commandValue = record["command"];
1496
- const command = Array.isArray(commandValue) ? commandValue.filter((entry) => typeof entry === "string").join(" ") : asString(commandValue) ?? "";
1497
- const cwd = asString(record["cwd"]);
1498
- const reason = trimText(record["reason"]);
1499
- const callId = asString(record["callId"]);
1500
- return {
1501
- command,
1502
- ...cwd ? { cwd } : {},
1503
- ...reason ? { reason } : {},
1504
- ...callId ? { callId } : {}
1505
- };
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;
1506
1440
  }
1507
- function describeFileChangeApproval(params) {
1508
- const record = isRecord3(params) ? params : {};
1509
- const reason = trimText(record["reason"]);
1510
- const grantRoot = asString(record["grantRoot"]);
1511
- const callId = asString(record["callId"]);
1512
- const detail = reason ?? describeFileChanges(record) ?? trimText(grantRoot);
1513
- return {
1514
- ...reason ? { reason } : {},
1515
- ...detail ? { detail } : {},
1516
- ...grantRoot ? { grantRoot } : {},
1517
- ...callId ? { callId } : {},
1518
- fileChanges: toApplyPatchFileChanges(record)
1519
- };
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);
1520
1450
  }
1521
- function describePermissionsApproval(params) {
1522
- const record = isRecord3(params) ? params : {};
1523
- const permissions = isRecord3(record["permissions"]) ? record["permissions"] : {};
1524
- const fileSystem = isRecord3(permissions["fileSystem"]) ? permissions["fileSystem"] : {};
1525
- const network = isRecord3(permissions["network"]) ? permissions["network"] : {};
1526
- const pathsOf = (value) => Array.isArray(value) ? value.flatMap((entry) => typeof entry === "string" && entry.length > 0 ? [entry] : []) : [];
1527
- const read = pathsOf(fileSystem["read"]);
1528
- const write = pathsOf(fileSystem["write"]);
1529
- const entries = Array.isArray(fileSystem["entries"]) ? fileSystem["entries"] : [];
1530
- for (const entry of entries) {
1531
- if (!isRecord3(entry) || !isRecord3(entry["path"])) continue;
1532
- const target = entry["path"];
1533
- const path = target["type"] === "path" ? asString(target["path"]) : target["type"] === "glob_pattern" ? asString(target["pattern"]) : void 0;
1534
- if (!path) continue;
1535
- if (entry["access"] === "read") read.push(path);
1536
- 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";
1537
1462
  }
1538
- const reason = trimText(record["reason"]);
1539
- return {
1540
- ...reason ? { reason } : {},
1541
- read: [...new Set(read)],
1542
- write: [...new Set(write)],
1543
- network: network["enabled"] === true
1544
- };
1545
1463
  }
1546
- function toPermissionsResponse(decision, params, persistent) {
1547
- if (decision !== "allow") return { permissions: {} };
1548
- const requested = isRecord3(params) && isRecord3(params["permissions"]) ? params["permissions"] : {};
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";
1469
+ }
1470
+ function codexCompactionId(threadId, turnId) {
1471
+ return `codex:${threadId}:${turnId ?? "session"}`;
1472
+ }
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"]);
1549
1486
  return {
1550
- permissions: {
1551
- ...requested["fileSystem"] !== void 0 ? { fileSystem: requested["fileSystem"] } : {},
1552
- ...requested["network"] !== void 0 ? { network: requested["network"] } : {}
1553
- },
1554
- scope: persistent ? "session" : "turn"
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
1555
1496
  };
1556
1497
  }
1557
- function describeMcpElicitation(params) {
1558
- const record = isRecord3(params) ? params : {};
1559
- const appName = asString(record["appName"]) ?? asString(record["serverName"]) ?? void 0;
1560
- const serverName = asString(record["serverName"]);
1561
- const message = trimText(record["message"]);
1562
- const url = asString(record["url"]) ?? asString(record["authorizationUrl"]);
1563
- const identity = appName ?? url ?? "unknown";
1564
- return {
1565
- ...appName ? { appName } : {},
1566
- ...serverName ? { serverName } : {},
1567
- ...message ? { message } : {},
1568
- ...url ? { url } : {},
1569
- identity,
1570
- mode: url ? "url" : "form",
1571
- ...record["requestedSchema"] !== void 0 ? { schema: record["requestedSchema"] } : {}
1572
- };
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 };
1573
1503
  }
1574
- function persistFromMeta(meta) {
1575
- if (!isRecord3(meta)) return void 0;
1576
- const persist = asString(meta["persist"]) ?? asString(meta["persistence"]);
1577
- if (persist === "session" || persist === "acceptForSession") return "session";
1578
- if (persist === "always" || persist === "permanent" || persist === "forever" || persist === "persistent" || persist === "acceptAlways") {
1579
- return "always";
1580
- }
1581
- return void 0;
1504
+
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;
1582
1515
  }
1583
- function toMcpElicitationResponse(decision, params) {
1584
- const described = describeMcpElicitation(params);
1585
- if (decision !== "allow" || described.mode === "url") {
1586
- return {
1587
- action: decision === "allow" ? "decline" : decision === "cancel" ? "cancel" : "decline"
1588
- };
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"
1544
+ };
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
+ ];
1589
1557
  }
1590
- const schema = isRecord3(described.schema) ? described.schema : void 0;
1591
- const properties = schema && isRecord3(schema["properties"]) ? schema["properties"] : {};
1592
- const required = schema && Array.isArray(schema["required"]) ? schema["required"] : [];
1593
- const content = {};
1594
- for (const [key, spec] of Object.entries(properties)) {
1595
- if (!isRecord3(spec)) continue;
1596
- if (Array.isArray(spec["oneOf"]) || Array.isArray(spec["enum"])) {
1597
- const options = Array.isArray(spec["oneOf"]) ? spec["oneOf"] : Array.isArray(spec["enum"]) ? spec["enum"] : [];
1598
- const first = options[0];
1599
- content[key] = isRecord3(first) ? first["const"] ?? first["value"] ?? first : first;
1600
- } else if (spec["type"] === "boolean") {
1601
- content[key] = true;
1602
- } else if (typeof spec["default"] !== "undefined") {
1603
- content[key] = spec["default"];
1604
- }
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
+ ];
1605
1575
  }
1606
- for (const key of required) {
1607
- if (typeof key === "string" && !(key in content)) {
1608
- return { action: "decline" };
1609
- }
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
+ ];
1610
1587
  }
1611
- const persist = persistFromMeta(isRecord3(params) ? params["_meta"] : void 0);
1612
- return {
1613
- action: "accept",
1614
- content,
1615
- ...persist ? { _meta: { persist } } : {}
1616
- };
1617
- }
1618
-
1619
- // src/usage/codexQuota.ts
1620
- var asRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
1621
- var asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : null;
1622
- var asNonEmptyString = (value) => typeof value === "string" && value.trim().length > 0 ? value.trim() : null;
1623
- var clampPercent = (value) => Math.min(100, Math.max(0, value));
1624
- var epochSecondsToIso = (value) => {
1625
- const seconds = asFiniteNumber(value);
1626
- if (seconds === null) return null;
1627
- const date = new Date(seconds * 1e3);
1628
- return Number.isNaN(date.getTime()) ? null : date.toISOString();
1629
- };
1630
- var SESSION_WINDOW_MINUTES = 5 * 60;
1631
- var WEEKLY_WINDOW_MINUTES = 7 * 24 * 60;
1632
- var formatWindowDuration = (minutes) => {
1633
- if (minutes % (24 * 60) === 0) return `${String(minutes / (24 * 60))}d`;
1634
- if (minutes % 60 === 0) return `${String(minutes / 60)}h`;
1635
- return `${String(minutes)}m`;
1636
- };
1637
- var classifyWindow = (minutes, slot) => {
1638
- if (minutes === SESSION_WINDOW_MINUTES) {
1639
- return { id: "session", label: { key: "usage.window.session" } };
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
+ ];
1640
1600
  }
1641
- if (minutes === WEEKLY_WINDOW_MINUTES) {
1642
- return { id: "weekly", label: { key: "usage.window.weekly" } };
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
+ ];
1643
1611
  }
1644
- if (minutes === null) return { id: slot, label: { key: "usage.window.limit" } };
1645
- return {
1646
- id: `window-${String(minutes)}m`,
1647
- label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
1648
- };
1649
- };
1650
- var mapWindow = (raw, slot, describe) => {
1651
- const window = asRecord(raw);
1652
- const usedPercent = asFiniteNumber(window?.["usedPercent"]);
1653
- if (window === null || usedPercent === null) return null;
1654
- const rawMinutes = asFiniteNumber(window["windowDurationMins"]);
1655
- const minutes = rawMinutes !== null && rawMinutes > 0 ? Math.trunc(rawMinutes) : null;
1656
- const { id, label } = describe(minutes, slot);
1657
- return {
1658
- id,
1659
- label,
1660
- usedPercent: clampPercent(usedPercent),
1661
- resetsAt: epochSecondsToIso(window["resetsAt"]),
1662
- ...minutes === null ? {} : { windowMs: minutes * 6e4 }
1663
- };
1664
- };
1665
- var PLAN_LABELS = {
1666
- free: "Free",
1667
- go: "Go",
1668
- plus: "Plus",
1669
- pro: "Pro",
1670
- prolite: "Pro Lite",
1671
- team: "Team",
1672
- business: "Business",
1673
- enterprise: "Enterprise",
1674
- edu: "Edu"
1675
- };
1676
- var planLabel = (planType) => {
1677
- const id = asNonEmptyString(planType);
1678
- if (id === null || id === "unknown") return void 0;
1679
- return PLAN_LABELS[id] ?? id.split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
1680
- };
1681
- var creditBalance = (value) => {
1682
- if (typeof value === "number") return Number.isFinite(value) ? value : null;
1683
- if (typeof value !== "string" || value.trim().length === 0) return null;
1684
- const numeric = Number(value.trim());
1685
- return Number.isFinite(numeric) ? numeric : null;
1686
- };
1687
- var MAIN_LIMIT_ID = "codex";
1688
- var CREDITS_BALANCE_ID = "credits";
1689
- var isMainCodexLimit = (snapshot) => {
1690
- const limitId = asNonEmptyString(asRecord(snapshot)?.["limitId"]);
1691
- return limitId === null || limitId === MAIN_LIMIT_ID;
1692
- };
1693
- var mapCodexSnapshotWindows = (snapshot) => {
1694
- const bucket = asRecord(snapshot);
1695
- if (bucket === null || !isMainCodexLimit(bucket)) return [];
1696
- const windows = [];
1697
- const takenIds = /* @__PURE__ */ new Set();
1698
- for (const slot of ["primary", "secondary"]) {
1699
- const window = mapWindow(bucket[slot], slot, classifyWindow);
1700
- if (window === null) continue;
1701
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1702
- takenIds.add(id);
1703
- windows.push({ ...window, id, group: MAIN_LIMIT_ID });
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
+ ];
1704
1624
  }
1705
- return windows;
1706
- };
1707
- var mapCodexRateLimits = (response, accountKey) => {
1708
- const root = asRecord(response);
1709
- const legacy = asRecord(root?.["rateLimits"]);
1710
- const byLimitId = asRecord(root?.["rateLimitsByLimitId"]);
1711
- const codexBucket = asRecord(byLimitId?.[MAIN_LIMIT_ID]);
1712
- const main = codexBucket ?? (legacy !== null && isMainCodexLimit(legacy) ? legacy : null);
1713
- if (root === null || legacy === null && codexBucket === null) return null;
1714
- const windows = mapCodexSnapshotWindows(main);
1715
- const takenIds = new Set(windows.map((window) => window.id));
1716
- const push = (window, slot) => {
1717
- if (window === null) return;
1718
- const id = takenIds.has(window.id) ? `${window.id}:${slot}` : window.id;
1719
- takenIds.add(id);
1720
- windows.push({ ...window, id });
1721
- };
1722
- const others = /* @__PURE__ */ new Map();
1723
- for (const [limitId, rawBucket] of Object.entries(byLimitId ?? {})) {
1724
- const bucket = asRecord(rawBucket);
1725
- if (limitId !== MAIN_LIMIT_ID && bucket !== null) others.set(limitId, bucket);
1726
- }
1727
- const legacyLimitId = asNonEmptyString(legacy?.["limitId"]);
1728
- if (legacy !== null && legacyLimitId !== null && !isMainCodexLimit(legacy)) {
1729
- if (!others.has(legacyLimitId)) others.set(legacyLimitId, legacy);
1730
- }
1731
- for (const [limitId, bucket] of others) {
1732
- const name = asNonEmptyString(bucket["limitName"]) ?? limitId;
1733
- for (const slot of ["primary", "secondary"]) {
1734
- const window = mapWindow(bucket[slot], slot, (minutes) => ({
1735
- id: `${limitId}:${slot}`,
1736
- label: minutes === null ? { text: name } : {
1737
- key: "usage.window.named",
1738
- values: { name, duration: formatWindowDuration(minutes) }
1739
- }
1740
- }));
1741
- push(window === null ? null : { ...window, group: limitId }, slot);
1742
- }
1743
- }
1744
- const balances = [];
1745
- const credits = asRecord(main?.["credits"]);
1746
- const balance = creditBalance(credits?.["balance"]);
1747
- if (credits !== null && credits["unlimited"] !== true && balance !== null) {
1748
- if (credits["hasCredits"] === true || balance > 0) {
1749
- balances.push({
1750
- id: CREDITS_BALANCE_ID,
1751
- label: { key: "usage.balance.credits" },
1752
- remaining: balance,
1753
- used: null,
1754
- limit: null,
1755
- unit: { kind: "credits" },
1756
- resetsAt: null
1757
- });
1758
- }
1759
- }
1760
- const plan = planLabel((main ?? legacy)?.["planType"]);
1761
- const resetSummary = asRecord(root["rateLimitResetCredits"]);
1762
- const availableCount = asFiniteNumber(resetSummary?.["availableCount"]);
1763
- return {
1764
- status: "available",
1765
- ...plan === void 0 ? {} : { plan },
1766
- windows,
1767
- balances,
1768
- accountKey,
1769
- // 回包没带摘要(老版本 CLI)就是不知道,不填;填 0 会让界面说「没有可用的重置额度」。
1770
- ...availableCount === null || availableCount < 0 ? {} : { resetCredits: { availableCount: Math.trunc(availableCount) } }
1771
- };
1772
- };
1773
- var codexQuotaFromRateLimits = (snapshot) => {
1774
- const bucket = asRecord(snapshot);
1775
- if (bucket === null) return null;
1776
- const reading = mapCodexRateLimits({ rateLimits: snapshot }, "");
1777
- if (reading === null || reading.status !== "available") return null;
1778
- const main = isMainCodexLimit(bucket);
1779
- const group = main ? MAIN_LIMIT_ID : asNonEmptyString(bucket["limitId"]);
1780
- const replaces = {
1781
- windowGroups: group === null ? [] : [group],
1782
- balanceIds: main ? [CREDITS_BALANCE_ID] : []
1783
- };
1784
- return {
1785
- windows: reading.windows,
1786
- balances: reading.balances,
1787
- ...reading.plan === void 0 ? {} : { plan: reading.plan },
1788
- replaces
1789
- };
1790
- };
1791
- var classifyRateLimitsError = (message) => {
1792
- const text = message.toLowerCase();
1793
- if (text.includes("chatgpt authentication required")) return "unsupportedLogin";
1794
- if (text.includes("authentication required")) return "missingCredentials";
1795
- if (/\b(401|403)\b|unauthorized|forbidden/.test(text)) return "authRejected";
1796
- if (/\b429\b|too many requests/.test(text)) return "rateLimited";
1797
- return "unreachable";
1798
- };
1799
- var CODEX_OFFICIAL_HOSTS = /* @__PURE__ */ new Set(["chatgpt.com"]);
1800
- var CODEX_DEFAULT_MODEL_PROVIDER = "openai";
1801
- var isOfficialUrl = (value) => {
1802
- const parsed = URL.parse(value.trim());
1803
- return parsed === null ? false : CODEX_OFFICIAL_HOSTS.has(parsed.hostname.toLowerCase());
1804
- };
1805
- var readTomlScalar = (raw) => {
1806
- const quoted = /^(["'])(.*?)\1/.exec(raw.trim());
1807
- if (quoted?.[2] !== void 0) return quoted[2];
1808
- const [beforeComment = ""] = raw.split("#");
1809
- return beforeComment.trim();
1810
- };
1811
- var TABLE_HEADER = /^\[\s*([^\][]+?)\s*\]\s*(?:#.*)?$/;
1812
- var KEY_VALUE = /^([A-Za-z0-9_.\-"']+)\s*=\s*(.*)$/;
1813
- var BASE_URL_KEYS = /* @__PURE__ */ new Set([
1814
- "chatgpt_base_url",
1815
- "openai_base_url",
1816
- "base_url"
1817
- ]);
1818
- var codexConfigSignal = (contents, options) => {
1819
- let table = null;
1820
- let selectedProvider = CODEX_DEFAULT_MODEL_PROVIDER;
1821
- const providerBaseUrls = /* @__PURE__ */ new Map();
1822
- for (const rawLine of contents.split("\n")) {
1823
- const line = rawLine.trim();
1824
- if (line.length === 0 || line.startsWith("#")) continue;
1825
- const header = TABLE_HEADER.exec(line);
1826
- if (header !== null) {
1827
- table = header[1] ?? "";
1828
- if (table === "model_providers") return "config.toml:model_providers";
1829
- continue;
1830
- }
1831
- const assignment = KEY_VALUE.exec(line);
1832
- if (assignment === null) continue;
1833
- const key = assignment[1] ?? "";
1834
- const value = readTomlScalar(assignment[2] ?? "");
1835
- if (table === null) {
1836
- if (key === "model_provider") {
1837
- selectedProvider = value;
1838
- } else if (key === "profile" || key.startsWith("model_providers")) {
1839
- return `config.toml:${key.split(".")[0] ?? key}`;
1840
- } else if (BASE_URL_KEYS.has(key) && !isOfficialUrl(value)) {
1841
- return `config.toml:${key}`;
1842
- }
1843
- continue;
1844
- }
1845
- if (table.startsWith("model_providers.") && key === "base_url") {
1846
- providerBaseUrls.set(
1847
- table.slice("model_providers.".length).replace(/^["']|["']$/g, ""),
1848
- value
1849
- );
1850
- }
1851
- }
1852
- const selected = options.providerOverridden ? CODEX_DEFAULT_MODEL_PROVIDER : selectedProvider;
1853
- if (selected === CODEX_DEFAULT_MODEL_PROVIDER) return null;
1854
- const baseUrl = providerBaseUrls.get(selected);
1855
- if (baseUrl === void 0 || !isOfficialUrl(baseUrl)) return "config.toml:model_provider";
1856
- return null;
1857
- };
1858
- var LAUNCH_ARG_MARKERS = [
1859
- "model_provider",
1860
- "chatgpt_base_url",
1861
- "openai_base_url",
1862
- "base_url",
1863
- "--profile"
1864
- ];
1865
- var codexLaunchArgsSignal = (launchArgs) => {
1866
- for (const marker of LAUNCH_ARG_MARKERS) {
1867
- if (launchArgs.includes(marker)) return `launchArgs:${marker}`;
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
+ ];
1868
1656
  }
1869
- return null;
1870
- };
1657
+ return [];
1658
+ }
1871
1659
 
1872
- // src/usageLimit.ts
1873
- var asRecord2 = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
1874
- var mergeCodexRateLimits = (previous, update) => {
1875
- const next = asRecord2(update);
1876
- if (next === void 0 || !isMainCodexLimit(next)) return previous;
1877
- if (previous === void 0) return next;
1878
- const merged = { ...previous };
1879
- for (const key of ["limitId", "planType", "rateLimitReachedType", "primary", "secondary"]) {
1880
- if (next[key] !== void 0) merged[key] = next[key];
1881
- }
1882
- return merged;
1883
- };
1884
- var formatUsageLimitWait = (waitMs) => {
1885
- const totalMinutes = Math.max(1, Math.ceil(waitMs / 6e4));
1886
- const days = Math.floor(totalMinutes / (24 * 60));
1887
- const hours = Math.floor(totalMinutes % (24 * 60) / 60);
1888
- const minutes = totalMinutes % 60;
1889
- if (days > 0) return hours === 0 ? `${String(days)}d` : `${String(days)}d ${String(hours)}h`;
1890
- if (hours === 0) return `${String(totalMinutes)}m`;
1891
- return minutes === 0 ? `${String(hours)}h` : `${String(hours)}h ${String(minutes)}m`;
1892
- };
1893
- var windowName = (window) => {
1894
- if (window.id === "session") return "5-hour";
1895
- if (window.id === "weekly") return "weekly";
1896
- return window.windowMs === void 0 ? "usage" : formatWindowDuration(window.windowMs / 6e4);
1897
- };
1898
- var nextStep = (rateLimitReachedType) => {
1899
- switch (rateLimitReachedType) {
1900
- case "workspace_owner_credits_depleted":
1901
- case "workspace_member_credits_depleted":
1902
- 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.";
1903
- case "workspace_owner_usage_limit_reached":
1904
- case "workspace_member_usage_limit_reached":
1905
- return " The workspace spend limit is reached: ask the workspace owner to raise it, or send the message again once the limit resets.";
1906
- default:
1907
- return void 0;
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
+ });
1908
1682
  }
1909
- };
1910
- var codexUsageLimitMessage = (snapshot, atIso, providerMessage) => {
1911
- const atMs = Date.parse(atIso);
1912
- let name;
1913
- let waitMs = 0;
1914
- let latestResetMs = Number.NEGATIVE_INFINITY;
1915
- if (snapshot !== void 0 && Number.isFinite(atMs)) {
1916
- for (const window of mapCodexSnapshotWindows(snapshot)) {
1917
- if (window.usedPercent < 100 || window.resetsAt === null) continue;
1918
- const resetMs = Date.parse(window.resetsAt);
1919
- if (!Number.isFinite(resetMs) || resetMs <= atMs || resetMs <= latestResetMs) continue;
1920
- latestResetMs = resetMs;
1921
- name = windowName(window);
1922
- 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
+ });
1923
1725
  }
1924
1726
  }
1925
- const step = nextStep(snapshot?.["rateLimitReachedType"]);
1926
- if (name === void 0) {
1927
- return `${providerMessage ?? "Codex usage limit reached."}${step ?? ""}`;
1928
- }
1929
- return `Codex usage limit reached. The ${name} limit resets in ${formatUsageLimitWait(waitMs)}.${step ?? " Send the message again once the limit resets."}`;
1930
- };
1931
- var isUsageLimitExceeded = (error) => asRecord2(error)?.["codexErrorInfo"] === "usageLimitExceeded";
1932
-
1933
- // src/events.ts
1934
- var CODEX_PROVIDER = "codex";
1935
- var EVENT_ID_PREFIX = `codex-evt-${NodeCrypto.randomUUID()}`;
1936
- var eventSeq = 0;
1937
- function nextEventId() {
1938
- eventSeq += 1;
1939
- return `${EVENT_ID_PREFIX}-${eventSeq}`;
1727
+ return out;
1940
1728
  }
1941
- function nowIso2() {
1942
- 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
+ };
1943
1742
  }
1944
- function itemIdOf(params) {
1945
- if (!isRecord3(params)) return void 0;
1946
- const item = isRecord3(params["item"]) ? params["item"] : params;
1947
- 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
+ };
1948
1756
  }
1949
- function itemStatusOf(params) {
1950
- if (!isRecord3(params)) return void 0;
1951
- const item = isRecord3(params["item"]) ? params["item"] : params;
1952
- 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
+ };
1953
1781
  }
1954
- function turnIdOf(params) {
1955
- if (!isRecord3(params)) return void 0;
1956
- 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
+ };
1957
1792
  }
1958
- function itemTitle(params) {
1959
- if (!isRecord3(params)) return void 0;
1960
- const item = isRecord3(params["item"]) ? params["item"] : params;
1961
- 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
+ };
1962
1809
  }
1963
- function itemDetail(params) {
1964
- if (!isRecord3(params)) return void 0;
1965
- const item = isRecord3(params["item"]) ? params["item"] : params;
1966
- const query = isRecord3(item["action"]) ? item["action"]["queries"] : item["query"];
1967
- if (Array.isArray(query)) {
1968
- const joined = query.filter((entry) => typeof entry === "string").join(", ");
1969
- 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";
1970
1816
  }
1971
- 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;
1972
1818
  }
1973
- function toTurnStatus(status) {
1974
- switch (status) {
1975
- case "failed":
1976
- return "failed";
1977
- case "cancelled":
1978
- case "canceled":
1979
- return "cancelled";
1980
- case "interrupted":
1981
- return "interrupted";
1982
- default:
1983
- 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
+ };
1984
1825
  }
1985
- }
1986
- var COLLAB_CHILD_STATUSES = /* @__PURE__ */ new Set([
1987
- "running",
1988
- "idle",
1989
- "failed",
1990
- "interrupted",
1991
- "cancelled"
1992
- ]);
1993
- function asCollabChildStatus(value) {
1994
- return typeof value === "string" && COLLAB_CHILD_STATUSES.has(value) ? value : void 0;
1995
- }
1996
- function collabTurnEndStatus(turnStatus) {
1997
- const state = toTurnStatus(turnStatus);
1998
- return state === "completed" ? "idle" : state;
1999
- }
2000
- function threadStateFromStatus(status) {
2001
- const type = isRecord3(status) ? asString(status["type"]) : asString(status);
2002
- if (type === "idle") return "idle";
2003
- if (type === "systemError" || type === "error") return "error";
2004
- return "active";
2005
- }
2006
- function codexCompactionId(threadId, turnId) {
2007
- return `codex:${threadId}:${turnId ?? "session"}`;
2008
- }
2009
- function normalizeCodexTokenUsage(tokenUsage) {
2010
- if (!isRecord3(tokenUsage)) return void 0;
2011
- const last = isRecord3(tokenUsage["last"]) ? tokenUsage["last"] : void 0;
2012
- const total = isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2013
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2014
- const usedTokens = count(last?.["totalTokens"]);
2015
- if (usedTokens === void 0 || usedTokens <= 0) return void 0;
2016
- const totalProcessedTokens = count(total?.["totalTokens"]);
2017
- const maxTokens = count(tokenUsage["modelContextWindow"]);
2018
- const inputTokens = count(last?.["inputTokens"]);
2019
- const cachedInputTokens = count(last?.["cachedInputTokens"]);
2020
- const outputTokens = count(last?.["outputTokens"]);
2021
- 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);
2022
1848
  return {
2023
- usedTokens,
2024
- lastUsedTokens: usedTokens,
2025
- ...totalProcessedTokens !== void 0 && totalProcessedTokens > usedTokens ? { totalProcessedTokens } : {},
2026
- ...maxTokens !== void 0 && maxTokens > 0 ? { maxTokens } : {},
2027
- ...inputTokens !== void 0 ? { inputTokens, lastInputTokens: inputTokens } : {},
2028
- ...cachedInputTokens !== void 0 ? { cachedInputTokens, lastCachedInputTokens: cachedInputTokens } : {},
2029
- ...outputTokens !== void 0 ? { outputTokens, lastOutputTokens: outputTokens } : {},
2030
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens, lastReasoningOutputTokens: reasoningOutputTokens } : {},
2031
- compactsAutomatically: true
1849
+ action: "accept",
1850
+ content,
1851
+ ...persist ? { _meta: { persist } } : {}
2032
1852
  };
2033
1853
  }
2034
- function turnCompletedControlFlow(params) {
2035
- const turn = isRecord3(params) ? isRecord3(params["turn"]) ? params["turn"] : params : {};
2036
- const state = toTurnStatus(asString(turn["status"]) ?? void 0);
2037
- const error = isRecord3(turn["error"]) ? trimText(turn["error"]["message"]) : void 0;
2038
- 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";
2039
1890
  }
1891
+
1892
+ // src/events/userInput.ts
2040
1893
  function toUserInputQuestions(params) {
2041
1894
  if (!isRecord3(params)) return void 0;
2042
1895
  const questions = Array.isArray(params["questions"]) ? params["questions"] : [];
@@ -2103,185 +1956,338 @@ function toCodexUserInputAnswers(answers) {
2103
1956
  out[questionId] = { answers: [...value] };
2104
1957
  continue;
2105
1958
  }
2106
- if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
2107
- out[questionId] = { answers: [...value["answers"]] };
2108
- continue;
1959
+ if (isRecord3(value) && Array.isArray(value["answers"]) && value["answers"].every((entry) => typeof entry === "string")) {
1960
+ out[questionId] = { answers: [...value["answers"]] };
1961
+ continue;
1962
+ }
1963
+ return void 0;
1964
+ }
1965
+ return out;
1966
+ }
1967
+ function toCanonicalUserInputAnswers(answers) {
1968
+ return Object.fromEntries(
1969
+ Object.entries(answers).map(([questionId, value]) => [
1970
+ questionId,
1971
+ value.answers.length === 1 ? value.answers[0] : [...value.answers]
1972
+ ])
1973
+ );
1974
+ }
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" } };
2002
+ return {
2003
+ id: `window-${String(minutes)}m`,
2004
+ label: { key: "usage.window.duration", values: { duration: formatWindowDuration(minutes) } }
2005
+ };
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 }
2020
+ };
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 });
2061
+ }
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
+ );
2109
2207
  }
2110
- return void 0;
2111
- }
2112
- return out;
2113
- }
2114
- function toCanonicalUserInputAnswers(answers) {
2115
- return Object.fromEntries(
2116
- Object.entries(answers).map(([questionId, value]) => [
2117
- questionId,
2118
- value.answers.length === 1 ? value.answers[0] : [...value.answers]
2119
- ])
2120
- );
2121
- }
2122
- function pathLeaf(value) {
2123
- if (!value) return void 0;
2124
- const parts = value.split("/").filter((part) => part.length > 0);
2125
- return parts.at(-1);
2126
- }
2127
- function eventRaw(source, method, payload) {
2128
- return { source, method, payload };
2129
- }
2130
- function baseEvent(input) {
2131
- return {
2132
- eventId: nextEventId(),
2133
- provider: CODEX_PROVIDER,
2134
- threadId: input.threadId,
2135
- createdAt: nowIso2(),
2136
- type: input.type,
2137
- payload: input.payload,
2138
- ...input.providerInstanceId ? { providerInstanceId: input.providerInstanceId } : {},
2139
- ...input.turnId ? { turnId: input.turnId } : {},
2140
- ...input.itemId ? { itemId: input.itemId } : {},
2141
- ...input.requestId ? { requestId: input.requestId } : {},
2142
- ...input.method ? {
2143
- raw: eventRaw(
2144
- input.source ?? "codex.app-server.notification",
2145
- input.method,
2146
- input.payload
2147
- )
2148
- } : {}
2149
- };
2150
- }
2151
- function mapCollabAgentEvent(input) {
2152
- if (!isRecord3(input.params)) return [];
2153
- const agentThreadId = asString(input.params["agentThreadId"]) ?? asString(input.params["threadId"]);
2154
- if (!agentThreadId) return [];
2155
- const agentPath = asString(input.params["agentPath"]) ?? asString(input.params["path"]) ?? void 0;
2156
- const nickname = asString(input.params["agentNickname"]) ?? asString(input.params["nickname"]);
2157
- const role = asString(input.params["agentRole"]) ?? asString(input.params["role"]) ?? pathLeaf(agentPath) ?? "general-purpose";
2158
- const knownName = nickname ?? pathLeaf(agentPath);
2159
- const title = knownName ?? agentThreadId;
2160
- const model = trimText(input.params["model"]);
2161
- const effort = trimText(input.params["effort"]);
2162
- const linkage = {
2163
- role,
2164
- ...knownName ? { title: knownName } : {},
2165
- ...model ? { model } : {},
2166
- ...effort ? { effort } : {},
2167
- ...agentPath ? { agentPath } : {},
2168
- timelineBypass: true,
2169
- rosterKind: "subagent"
2170
- };
2171
- const threadId = input.rootThreadId;
2172
- const method = input.method;
2173
- if (method === "collabAgent/started" || method === "task.started") {
2174
- return [
2175
- baseEvent({
2176
- type: "task.started",
2177
- threadId,
2178
- payload: { taskId: agentThreadId, ...linkage },
2179
- method,
2180
- providerInstanceId: input.providerInstanceId
2181
- })
2182
- ];
2183
- }
2184
- if (method === "collabAgent/progress" || method === "task.progress") {
2185
- const summary = trimText(input.params["detail"]) ?? trimText(input.params["text"]);
2186
- return [
2187
- baseEvent({
2188
- type: "task.progress",
2189
- threadId,
2190
- // 契约 TaskProgressPayload 的 description 是必填;子代理行靠它显示名字。
2191
- payload: {
2192
- taskId: agentThreadId,
2193
- description: title,
2194
- ...summary ? { summary } : {},
2195
- ...linkage
2196
- },
2197
- method,
2198
- providerInstanceId: input.providerInstanceId
2199
- })
2200
- ];
2201
2208
  }
2202
- if (method === "collabAgent/completed" || method === "task.completed") {
2203
- const status = collabTurnEndStatus(asString(input.params["turnStatus"]) ?? void 0);
2204
- return [
2205
- baseEvent({
2206
- type: "task.updated",
2207
- threadId,
2208
- payload: { taskId: agentThreadId, status, ...linkage },
2209
- method,
2210
- providerInstanceId: input.providerInstanceId
2211
- })
2212
- ];
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}`;
2213
2225
  }
2214
- if (method === "collabAgent/statusChanged") {
2215
- const raw = asString(input.params["status"]);
2216
- const status = raw === "systemError" || raw === "error" ? "failed" : raw === "idle" ? "idle" : "running";
2217
- return [
2218
- baseEvent({
2219
- type: "task.updated",
2220
- threadId,
2221
- payload: { taskId: agentThreadId, status, ...linkage },
2222
- method,
2223
- providerInstanceId: input.providerInstanceId
2224
- })
2225
- ];
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];
2226
2238
  }
2227
- if (method === "collabAgent/metadataUpdated") {
2228
- return [
2229
- baseEvent({
2230
- type: "task.updated",
2231
- threadId,
2232
- payload: { taskId: agentThreadId, ...linkage },
2233
- method,
2234
- providerInstanceId: input.providerInstanceId
2235
- })
2236
- ];
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;
2237
2265
  }
2238
- if (method === "collabAgent/metadata") {
2239
- const status = asCollabChildStatus(input.params["status"]);
2240
- if (status === void 0) return [];
2241
- return [
2242
- baseEvent({
2243
- type: "task.updated",
2244
- threadId,
2245
- payload: { taskId: agentThreadId, status, ...linkage },
2246
- method,
2247
- providerInstanceId: input.providerInstanceId
2248
- })
2249
- ];
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
+ }
2250
2281
  }
2251
- if (method === "collabAgent/tokenUsage") {
2252
- const tokenUsage = isRecord3(input.params["tokenUsage"]) ? input.params["tokenUsage"] : void 0;
2253
- const total = tokenUsage && isRecord3(tokenUsage["total"]) ? tokenUsage["total"] : void 0;
2254
- const count = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
2255
- const totalTokens = total ? count(total["totalTokens"]) : void 0;
2256
- if (total === void 0 || totalTokens === void 0) return [];
2257
- const inputTokens = count(total["inputTokens"]);
2258
- const cachedInputTokens = count(total["cachedInputTokens"]);
2259
- const outputTokens = count(total["outputTokens"]);
2260
- const reasoningOutputTokens = count(total["reasoningOutputTokens"]);
2261
- const typedUsage = {
2262
- totalTokens,
2263
- ...inputTokens !== void 0 ? { inputTokens } : {},
2264
- ...cachedInputTokens !== void 0 ? { cachedInputTokens } : {},
2265
- ...outputTokens !== void 0 ? { outputTokens } : {},
2266
- ...reasoningOutputTokens !== void 0 ? { reasoningOutputTokens } : {}
2267
- };
2268
- return [
2269
- baseEvent({
2270
- type: "task.progress",
2271
- threadId,
2272
- payload: {
2273
- taskId: agentThreadId,
2274
- description: title,
2275
- typedUsage,
2276
- ...linkage
2277
- },
2278
- method,
2279
- providerInstanceId: input.providerInstanceId
2280
- })
2281
- ];
2282
+ const step = nextStep(snapshot?.["rateLimitReachedType"]);
2283
+ if (name === void 0) {
2284
+ return `${providerMessage ?? "Codex usage limit reached."}${step ?? ""}`;
2282
2285
  }
2283
- return [];
2284
- }
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
2285
2291
  function mapNotificationToEvents(input) {
2286
2292
  const { method, params, threadId, providerInstanceId, rateLimits } = input;
2287
2293
  if (method.startsWith("collabAgent/")) {
@@ -2851,6 +2857,8 @@ function mapNotificationToEvents(input) {
2851
2857
  return [];
2852
2858
  }
2853
2859
  }
2860
+
2861
+ // src/events/serverRequests.ts
2854
2862
  function toRequestTypeFromKind(kind) {
2855
2863
  switch (kind) {
2856
2864
  case "command":