@epoch-agent/core 1.0.0 → 2.0.0

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 (4) hide show
  1. package/README.md +35 -8
  2. package/dist/index.d.ts +1511 -1157
  3. package/dist/index.js +1116 -564
  4. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- import { PROVIDER_TYPES, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, isSettingsPaneId, SETTINGS_PANE_IDS, PERMISSION_LEVELS, SHELL_KINDS, cronTimes, SCHEDULE_MAX_TIMES_PER_DAY, SCHEDULE_INTERVAL_MIN_MINUTES, SCHEDULE_INTERVAL_MAX_MINUTES, ToolExposure, MAX_OPTIONS, MIN_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, parseModelRef, isWirePluginArchiveName, systemNote, formatMoney, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, apiKeyEnvVar, PROVIDER_INFOS, isProviderType, API_KEY_ENV_VARS, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, truncateToBudget, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, imagePartOmittedSummary, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
1
+ import { REASONING_EFFORTS, MODEL_ENTRY_PROTOCOLS, PROVIDER_TYPES, isModelEntryId, parseModelRef, OPERATION_TYPES as OPERATION_TYPES$1, COMPLIANCE_CATEGORIES, COMPLIANCE_ACTIONS, SEARCH_PROVIDER_TYPES, CURRENCIES, LANGS, isSettingsPaneId, SETTINGS_PANE_IDS, BRAND_WHEN_SIDEBAR_COLLAPSED, PERMISSION_LEVELS, SHELL_KINDS, cronTimes, SCHEDULE_MAX_TIMES_PER_DAY, SCHEDULE_INTERVAL_MIN_MINUTES, SCHEDULE_INTERVAL_MAX_MINUTES, ToolExposure, MIN_OPTIONS, MAX_OPTIONS, MAX_HEADER_CHARS, MAX_QUESTIONS, isPermissionLevel, SCHEDULE_DRAFT_TOOL, isValidAgentRoleName, DEFAULT_AGENT_ROLE, isValidCommandName, RESERVED_COMMAND_NAMES, isWirePluginArchiveName, systemNote, formatMoney, totalUsageTokens, NOOP_TELEMETRY, METRIC, contentToText, GEN_AI, isPromptOversize, promptTokens, entrySecretName, isEntrySecretName, entryRef, KEY_ACTIONS, tableOf, perAction, isKeyAction, parseSequence, RESERVED_CHORDS, chordId, sequenceId, ACTION_CONTEXTS, SEQUENCE_CONTEXTS, assertNeverPart, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_REFS, utf8ByteLength, truncateToBudget, attachSessionSurface, isContextNearlyFull, isOperationType, normalizeApproval, imagePartOmittedSummary, ENTRY_REF_PREFIX, MAX_ATTACH_BYTES, SIMULTANEOUS_CONTEXTS, SESSION_REFERENCE_ERROR_CODES, filePartSummary, sessionPartSummary } from '@epoch-agent/protocol';
2
2
  export { API_KEY_ENV_VARS, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, ToolExposure, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
3
3
  import { existsSync, statSync, readdirSync, readFileSync, mkdirSync, writeFileSync, rmSync, renameSync, realpathSync, symlinkSync, cpSync, lstatSync, unlinkSync, rmdirSync, mkdtempSync, copyFileSync, openSync, writeSync, closeSync, readSync } from 'fs';
4
4
  import { homedir, platform, tmpdir, hostname } from 'os';
5
5
  import { join, dirname, resolve, relative, isAbsolute, extname, sep, basename } from 'path';
6
6
  import { execFile, execSync, execFileSync, spawn } from 'child_process';
7
- import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, PROJECT_DIR_NAME, guardSpawn, t, suggestKey, misspelledKeyIssues, openDatabase, migrate, releaseDatabase, parseLenient, issueDetails, splitWords, unknownKeyIssues, checkpointsDir, expandPath, pluginsDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSettingsPath, projectLocalSettingsPath, projectPoliciesDir, trustedImportsPath, canProbeGit, budgetStatePath, artifactsDir, isInWorkspace, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, getSecretValues, listStoredSecretNames, maskApiKey, setSectionField, countCodePoints, headCodePoints, attachmentsDir, workspacesPath, samePath, lastSessionsPath, appearancePath, automationLogsDir, projectComplianceDir, projectSkillsDir, trustPath, projectCommandsDir, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, envPath, checkDangerousCommand, checkObfuscation, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, tailCodePoints } from '@epoch-agent/infra';
7
+ import { createLogger, dbPath, resolveHomeDir, DEFAULT_MAX_TURNS, PROJECT_DIR_NAME, guardSpawn, t, suggestKey, misspelledKeyIssues, openDatabase, migrate, releaseDatabase, parseLenient, issueDetails, splitWords, unknownKeyIssues, checkpointsDir, expandPath, pluginsDir, projectHooksPath, projectClaudeSettingsPath, projectClaudeLocalSettingsPath, projectSettingsPath, projectLocalSettingsPath, projectPoliciesDir, trustedImportsPath, canProbeGit, budgetStatePath, artifactsDir, isInWorkspace, UNLIMITED_TURNS, turnLimitOf, configPath, managedSettingsPath, listStoredSecretNames, maskApiKey, unsetSectionField, setSectionField, countCodePoints, headCodePoints, attachmentsDir, workspacesPath, samePath, lastSessionsPath, appearancePath, automationLogsDir, projectComplianceDir, projectSkillsDir, trustPath, projectCommandsDir, detectBackend, EXEC_PATH_AS_NODE_ENV, isolate, createStreamDecoder, parseShellCommand, writeArtifact, getSecretValues, envPath, checkDangerousCommand, checkObfuscation, keepHeadAndTail, normalizeForMatch, isReadOnlyCommand, CODE_EXEC_ROOTS, tailCodePoints } from '@epoch-agent/infra';
8
8
  export { IS_WINDOWS, WINDOWS_HIDE_FLAGS, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, createLogger, detectBackend, getDefaultShell, getPythonCommand, isInWorkspace, isReadOnlyCommand, isolate, maskApiKey, probeBubblewrap, probeSeatbelt, complianceDir as resolveComplianceDir, policiesDir as resolvePoliciesDir, setLogLevel } from '@epoch-agent/infra';
9
9
  import { createRequire } from 'module';
10
10
  import { randomUUID, createHash, randomBytes } from 'crypto';
@@ -87,9 +87,9 @@ function isValidInterval(everyMinutes) {
87
87
  function normalizeTrigger(trigger) {
88
88
  if (trigger.kind !== "cron") return trigger;
89
89
  const [at = trigger.at, ...rest] = cronTimes(trigger);
90
- const base = { ...trigger };
91
- delete base.alsoAt;
92
- return rest.length > 0 ? { ...base, at, alsoAt: rest } : { ...base, at };
90
+ const base2 = { ...trigger };
91
+ delete base2.alsoAt;
92
+ return rest.length > 0 ? { ...base2, at, alsoAt: rest } : { ...base2, at };
93
93
  }
94
94
  function validateWindow(trigger, startDate, endDate) {
95
95
  const issues = [];
@@ -421,21 +421,21 @@ function triggerShape(tag, body2) {
421
421
  const start = tagText(body2, "StartBoundary");
422
422
  const time = /T(\d{2}:\d{2})/.exec(start)?.[1] ?? "";
423
423
  const date = /^(\d{4}-\d{2}-\d{2})/.exec(start)?.[1] ?? "";
424
- const base = { time, date, days: "", everyMinutes: 0 };
424
+ const base2 = { time, date, days: "", everyMinutes: 0 };
425
425
  const interval = tagText(body2, "Interval");
426
- if (interval) return { ...base, kind: "EVERY", everyMinutes: isoDurationMinutes(interval) };
427
- if (/<ScheduleByDay>/i.test(body2)) return { ...base, kind: "DAILY" };
426
+ if (interval) return { ...base2, kind: "EVERY", everyMinutes: isoDurationMinutes(interval) };
427
+ if (/<ScheduleByDay>/i.test(body2)) return { ...base2, kind: "DAILY" };
428
428
  if (/<ScheduleByWeek>/i.test(body2)) {
429
429
  const days = WEEKDAY_NAMES.flatMap(
430
430
  (name, idx) => new RegExp(`<${name}\\s*/>`, "i").test(body2) ? [WEEKDAY_CODES[idx]] : []
431
431
  );
432
- return { ...base, kind: "WEEKLY", days: days.join(",") };
432
+ return { ...base2, kind: "WEEKLY", days: days.join(",") };
433
433
  }
434
434
  if (/<ScheduleByMonth>/i.test(body2)) {
435
435
  const days = [...body2.matchAll(/<Day>\s*(\d+)\s*<\/Day>/gi)].map((m) => Number(m[1]));
436
- return { ...base, kind: "MONTHLY", days: days.sort((a, b) => a - b).join(",") };
436
+ return { ...base2, kind: "MONTHLY", days: days.sort((a, b) => a - b).join(",") };
437
437
  }
438
- return { ...base, kind: /^TimeTrigger$/i.test(tag) && date ? "ONCE" : "" };
438
+ return { ...base2, kind: /^TimeTrigger$/i.test(tag) && date ? "ONCE" : "" };
439
439
  }
440
440
  function cronSpec(kind, times, days) {
441
441
  const at = times.join(",");
@@ -903,11 +903,11 @@ var NO_REVEALED_TOOLS = {
903
903
  isRevealed: () => false,
904
904
  names: () => []
905
905
  };
906
- function revealScopedProvider(base, deferred, reveal) {
906
+ function revealScopedProvider(base2, deferred, reveal) {
907
907
  const revealed = () => deferred().filter((tool) => reveal.isRevealed(tool.name));
908
908
  return {
909
- list: () => [...base.list(), ...revealed()],
910
- get: (name) => base.get(name) ?? revealed().find((tool) => tool.name === name)
909
+ list: () => [...base2.list(), ...revealed()],
910
+ get: (name) => base2.get(name) ?? revealed().find((tool) => tool.name === name)
911
911
  };
912
912
  }
913
913
  // src/tools/registry.ts
@@ -1158,6 +1158,14 @@ function parsePage(raw) {
1158
1158
  const label = typeof raw["label"] === "string" ? raw["label"].trim() : "";
1159
1159
  return label ? { url: rawUrl, label } : { url: rawUrl };
1160
1160
  }
1161
+ function checkOptionCount(count, at, page, rawPage) {
1162
+ if (count === 0 && page) return void 0;
1163
+ if (count === 0 && rawPage !== void 0 && rawPage !== null) {
1164
+ return `questions[${at}] \u7684 page \u6CA1\u88AB\u6536\u4E0B\uFF08url \u53EA\u6536\u672C\u673A\u56DE\u73AF http(s)://127.0.0.1 \u6216 localhost\uFF09\uFF0C\u800C\u8FD9\u4E00\u95EE\u6CA1\u6709\u9009\u9879\u3002\u8981\u4E48\u628A url \u6539\u5BF9\uFF0C\u8981\u4E48\u7ED9 ${MIN_OPTIONS}\u2013${MAX_OPTIONS} \u4E2A\u9009\u9879`;
1165
+ }
1166
+ if (count >= MIN_OPTIONS && count <= MAX_OPTIONS) return void 0;
1167
+ return `questions[${at}] \u6709 ${count} \u4E2A\u9009\u9879\uFF0C\u8981 ${MIN_OPTIONS}\u2013${MAX_OPTIONS} \u4E2A\uFF08\u53EA\u6709\u5E26 page \u7684\u90A3\u4E00\u95EE\u53EF\u4EE5\u4E00\u4E2A\u90FD\u4E0D\u7ED9\uFF09\u3002**\u4E0D\u8981**\u81EA\u5DF1\u52A0\u300C\u5176\u4ED6\u300D\u9009\u9879 \u2014\u2014 \u81EA\u7531\u8F93\u5165\u7531\u754C\u9762\u81EA\u52A8\u63D0\u4F9B`;
1168
+ }
1161
1169
  function parseQuestion(raw, at) {
1162
1170
  if (!isRecord(raw)) return `questions[${at}] \u4E0D\u662F\u5BF9\u8C61`;
1163
1171
  const question = typeof raw["question"] === "string" ? raw["question"].trim() : "";
@@ -1167,15 +1175,14 @@ function parseQuestion(raw, at) {
1167
1175
  if (header.length > MAX_HEADER_CHARS) {
1168
1176
  return `questions[${at}] \u7684 header \u6709 ${header.length} \u5B57\uFF0C\u8D85\u8FC7 ${MAX_HEADER_CHARS} \u4E0A\u9650`;
1169
1177
  }
1170
- const rawOptions = raw["options"];
1178
+ const page = parsePage(raw["page"]);
1179
+ const rawOptions = raw["options"] ?? (page ? [] : void 0);
1171
1180
  if (!Array.isArray(rawOptions)) return `questions[${at}] \u7684 options \u4E0D\u662F\u6570\u7EC4`;
1172
1181
  const options = rawOptions.map(parseOption);
1173
1182
  const badAt = options.indexOf(null);
1174
1183
  if (badAt >= 0) return `questions[${at}].options[${badAt}] \u7F3A label`;
1175
- if (options.length < MIN_OPTIONS || options.length > MAX_OPTIONS) {
1176
- return `questions[${at}] \u6709 ${options.length} \u4E2A\u9009\u9879\uFF0C\u8981 ${MIN_OPTIONS}\u2013${MAX_OPTIONS} \u4E2A\u3002**\u4E0D\u8981**\u81EA\u5DF1\u52A0\u300C\u5176\u4ED6\u300D\u9009\u9879 \u2014\u2014 \u81EA\u7531\u8F93\u5165\u7531\u754C\u9762\u81EA\u52A8\u63D0\u4F9B`;
1177
- }
1178
- const page = parsePage(raw["page"]);
1184
+ const optionError = checkOptionCount(options.length, at, page, raw["page"]);
1185
+ if (optionError !== void 0) return optionError;
1179
1186
  return {
1180
1187
  question,
1181
1188
  header,
@@ -1244,9 +1251,8 @@ var PARAMETERS = {
1244
1251
  },
1245
1252
  options: {
1246
1253
  type: "array",
1247
- minItems: MIN_OPTIONS,
1248
1254
  maxItems: MAX_OPTIONS,
1249
- description: "\u4E0D\u8981\u81EA\u5DF1\u52A0\u300C\u5176\u4ED6\u300D\u2014\u2014 \u754C\u9762\u4F1A\u81EA\u52A8\u63D0\u4F9B\u81EA\u7531\u8F93\u5165",
1255
+ description: `${MIN_OPTIONS}\u2013${MAX_OPTIONS} \u4E2A\uFF1B\u5E26 page \u65F6\u53EF\u4EE5\u4E0D\u7ED9\u3002\u4E0D\u8981\u81EA\u5DF1\u52A0\u300C\u5176\u4ED6\u300D\u2014\u2014 \u754C\u9762\u4F1A\u81EA\u52A8\u63D0\u4F9B\u81EA\u7531\u8F93\u5165`,
1250
1256
  items: {
1251
1257
  type: "object",
1252
1258
  properties: {
@@ -1270,7 +1276,7 @@ var PARAMETERS = {
1270
1276
  required: ["url"]
1271
1277
  }
1272
1278
  },
1273
- required: ["question", "header", "options", "multiSelect"]
1279
+ required: ["question", "header", "multiSelect"]
1274
1280
  }
1275
1281
  }
1276
1282
  },
@@ -1286,7 +1292,7 @@ var DESCRIPTION = [
1286
1292
  "\u9009\u9879\u91CC**\u4E0D\u8981**\u81EA\u5DF1\u653E\u300C\u5176\u4ED6\u300D\uFF1A\u754C\u9762\u4F1A\u81EA\u52A8\u63D0\u4F9B\u81EA\u7531\u8F93\u5165\u3002",
1287
1293
  "\u26A0\uFE0F \u63D2\u4EF6\u7684\u5DE5\u5177\u7ED9\u4E86\u4F60\u4E00\u4E2A**\u672C\u673A\u5730\u5740**\u3001\u800C\u90A3\u4E2A\u9009\u62E9\u7528\u51E0\u4E2A\u9009\u9879\u8BF4\u4E0D\u6E05\u65F6\uFF08\u51E0\u5341\u5F20\u56FE\u91CC\u6311\u4E00\u5F20\u3001",
1288
1294
  "\u4E00\u5F20\u8868\u91CC\u5708\u51E0\u884C\uFF09\uFF0C\u628A\u5730\u5740\u653E\u8FDB `questions[].page` \u2014\u2014 \u7528\u6237\u4F1A\u5728\u5F39\u7A97\u91CC\u76F4\u63A5\u770B\u5230\u90A3\u4E00\u9875\u3002",
1289
- "\u5B83**\u9644\u52A0\u5728\u9009\u9879\u4E4B\u5916**\uFF0C\u9009\u9879\u7167\u65E7\u8981\u7ED9\uFF1A\u7EC8\u7AEF\u91CC\u753B\u4E0D\u51FA\u9875\u9762\uFF0C\u90A3\u65F6\u7528\u6237\u770B\u5230\u7684\u53EA\u6709\u4F60\u5199\u7684\u9009\u9879\u3002"
1295
+ "\u5E26\u4E86 page \u7684\u90A3\u4E00\u95EE\uFF0C`options` \u53EF\u4EE5\u4E0D\u7ED9\u3002"
1290
1296
  ].join("");
1291
1297
  function createAskQuestionTool(deps) {
1292
1298
  return {
@@ -1314,7 +1320,9 @@ function createAskQuestionTool(deps) {
1314
1320
  "**\u6309\u4F60\u81EA\u5DF1\u7684\u5224\u65AD\u6311\u6700\u5408\u7406\u7684\u9ED8\u8BA4\u7EE7\u7EED\u505A**\uFF0C\u5E76\u5728\u56DE\u7B54\u91CC\u8BF4\u660E\u4F60\u9009\u4E86\u4EC0\u4E48\u3001\u4E3A\u4EC0\u4E48\u3002",
1315
1321
  "\u4F60\u672C\u6765\u60F3\u95EE\u7684\u662F\uFF1A",
1316
1322
  ...parsed.questions.map(
1317
- (q) => `- ${q.question}\uFF08${q.options.map((o) => o.label).join(" / ")}\uFF09`
1323
+ (q) => (
1324
+ `- ${q.question}\uFF08${q.options.length > 0 ? q.options.map((o) => o.label).join(" / ") : q.page?.url}\uFF09`
1325
+ )
1318
1326
  ),
1319
1327
  "",
1320
1328
  "\u4E0B\u6B21\u9047\u5230\u540C\u7C7B\u5206\u53C9\uFF0C\u628A\u9700\u8981\u7684\u4FE1\u606F\u5199\u8FDB\u63D0\u793A\u8BCD\u4F1A\u66F4\u7701\u4E00\u8F6E\u3002"
@@ -2441,22 +2449,22 @@ function timeoutMessage(timeoutMs, toolCalls) {
2441
2449
  return timeoutNotice(Math.round(timeoutMs / 1e3), toolCalls);
2442
2450
  }
2443
2451
  function toOutcome(ending, ctx) {
2444
- const base = { value: "", printed: ctx.printed, toolCalls: ctx.toolCalls };
2452
+ const base2 = { value: "", printed: ctx.printed, toolCalls: ctx.toolCalls };
2445
2453
  if (ctx.capped) {
2446
2454
  return {
2447
- ...base,
2455
+ ...base2,
2448
2456
  ok: false,
2449
2457
  error: { kind: "cap", message: capNotice(ctx.limits.maxToolCalls) }
2450
2458
  };
2451
2459
  }
2452
2460
  if (!ending) {
2453
- return { ...base, ok: false, error: { kind: "throw", message: noResultNotice() } };
2461
+ return { ...base2, ok: false, error: { kind: "throw", message: noResultNotice() } };
2454
2462
  }
2455
- if (ending.kind === "done") return { ...base, ok: true, value: ending.value };
2463
+ if (ending.kind === "done") return { ...base2, ok: true, value: ending.value };
2456
2464
  if (ending.kind === "failed") {
2457
- return { ...base, ok: false, error: { kind: "throw", message: ending.message } };
2465
+ return { ...base2, ok: false, error: { kind: "throw", message: ending.message } };
2458
2466
  }
2459
- return { ...base, ok: false, error: { kind: ending.kind, message: ending.message } };
2467
+ return { ...base2, ok: false, error: { kind: ending.kind, message: ending.message } };
2460
2468
  }
2461
2469
  // src/tools/builtin/run-code.ts
2462
2470
  var RUN_CODE_TOOL = "run_code";
@@ -2940,9 +2948,9 @@ function parseScheduleDraft(raw) {
2940
2948
  }
2941
2949
  function draftWidening(draft, current) {
2942
2950
  const out = [];
2943
- const base = draft.action === "update" && current ? current.permission === "bypass" ? "auto" : current.permission : "default";
2951
+ const base2 = draft.action === "update" && current ? current.permission === "bypass" ? "auto" : current.permission : "default";
2944
2952
  const level = draft.permission;
2945
- if (level !== void 0 && PERMISSION_ORDER.indexOf(level) > PERMISSION_ORDER.indexOf(base)) {
2953
+ if (level !== void 0 && PERMISSION_ORDER.indexOf(level) > PERMISSION_ORDER.indexOf(base2)) {
2946
2954
  out.push("permission");
2947
2955
  }
2948
2956
  const has = (list, x) => list?.includes(x) ?? false;
@@ -3195,18 +3203,18 @@ function contentMatchesCommand(content, text) {
3195
3203
  }
3196
3204
  function resolvePathPattern(content, workDir) {
3197
3205
  let raw = content;
3198
- let base = workDir;
3206
+ let base2 = workDir;
3199
3207
  if (raw.startsWith("//")) {
3200
3208
  raw = raw.slice(1);
3201
- base = "";
3209
+ base2 = "";
3202
3210
  } else if (raw.startsWith("~/")) {
3203
3211
  raw = raw.slice(2);
3204
- base = homedir();
3212
+ base2 = homedir();
3205
3213
  }
3206
3214
  const wildcardAt = raw.search(/[*?]/);
3207
3215
  const head = wildcardAt === -1 ? raw : raw.slice(0, wildcardAt);
3208
3216
  const tail = wildcardAt === -1 ? "" : raw.slice(wildcardAt);
3209
- const resolvedHead = base === "" && isAbsolute(head) ? resolve(head) : resolve(base || ".", head);
3217
+ const resolvedHead = base2 === "" && isAbsolute(head) ? resolve(head) : resolve(base2 || ".", head);
3210
3218
  const sep8 = head.endsWith("/") || head.endsWith("\\") ? "/" : "";
3211
3219
  return `${resolvedHead}${sep8}${tail}`;
3212
3220
  }
@@ -3807,10 +3815,10 @@ function belowTrigramFloor(safeQuery) {
3807
3815
  // src/session/trace.ts
3808
3816
  var NODE_COLUMNS = `id, title, cwd, started_at, message_count, archived, parent_id`;
3809
3817
  function toNode(ctx, row, depth) {
3810
- const base = { sessionId: row.id, depth, parentId: row.parent_id ?? void 0 };
3811
- if (!ctx.visible(row.id)) return { ...base, visible: false };
3818
+ const base2 = { sessionId: row.id, depth, parentId: row.parent_id ?? void 0 };
3819
+ if (!ctx.visible(row.id)) return { ...base2, visible: false };
3812
3820
  return {
3813
- ...base,
3821
+ ...base2,
3814
3822
  visible: true,
3815
3823
  title: row.title,
3816
3824
  cwd: row.cwd ?? void 0,
@@ -3970,11 +3978,11 @@ var SessionQueries = class {
3970
3978
  const meta = this.db.prepare(`SELECT s.title FROM sessions s WHERE s.id = @sid AND ${vis.clause}`).get({ ...vis.params, sid: input.sessionId });
3971
3979
  if (!meta) return { authorized: false, title: "", before: [], after: [], total: 0 };
3972
3980
  const total = this.db.prepare(`SELECT count(*) AS n FROM messages WHERE session_id = @sid`).get({ sid: input.sessionId }).n;
3973
- const base = { authorized: true, title: meta.title, total };
3981
+ const base2 = { authorized: true, title: meta.title, total };
3974
3982
  const anchor = this.recordRow(input.sessionId, input.messageId);
3975
- if (!anchor) return { ...base, before: [], after: [] };
3983
+ if (!anchor) return { ...base2, before: [], after: [] };
3976
3984
  return {
3977
- ...base,
3985
+ ...base2,
3978
3986
  anchor: toRecord(anchor),
3979
3987
  before: this.neighbourRows(input.sessionId, input.messageId, "before", input.before).reverse().map(toRecord),
3980
3988
  after: this.neighbourRows(input.sessionId, input.messageId, "after", input.after).map(
@@ -5693,11 +5701,11 @@ function pushNarrowed(notices, kind, incoming, wanted, kept) {
5693
5701
  cut: wanted.filter((n) => !keptSet.has(n))
5694
5702
  });
5695
5703
  }
5696
- function intersectNames(base, incoming) {
5697
- if (!base && !incoming) return void 0;
5698
- if (!base) return incoming ? [...incoming] : void 0;
5699
- if (!incoming) return [...base];
5700
- const baseSet = new Set(base);
5704
+ function intersectNames(base2, incoming) {
5705
+ if (!base2 && !incoming) return void 0;
5706
+ if (!base2) return incoming ? [...incoming] : void 0;
5707
+ if (!incoming) return [...base2];
5708
+ const baseSet = new Set(base2);
5701
5709
  return incoming.filter((t81) => baseSet.has(t81));
5702
5710
  }
5703
5711
  function findRole(roles, name) {
@@ -5842,7 +5850,7 @@ function describe2(err, path, lang) {
5842
5850
  return t("role_write.remove_failed", { path, reason: reason9 }, lang);
5843
5851
  }
5844
5852
  // src/agent-role/scope.ts
5845
- function roleScopedProvider(base, role, blocked) {
5853
+ function roleScopedProvider(base2, role, blocked) {
5846
5854
  const blockedSet = new Set(blocked);
5847
5855
  const allow = role?.tools ? new Set(role.tools) : void 0;
5848
5856
  const permitted = (name) => {
@@ -5850,14 +5858,14 @@ function roleScopedProvider(base, role, blocked) {
5850
5858
  return allow ? allow.has(name) : true;
5851
5859
  };
5852
5860
  return {
5853
- list: () => base.list().filter((t81) => permitted(t81.name)),
5854
- get: (name) => permitted(name) ? base.get(name) : void 0
5861
+ list: () => base2.list().filter((t81) => permitted(t81.name)),
5862
+ get: (name) => permitted(name) ? base2.get(name) : void 0
5855
5863
  };
5856
5864
  }
5857
- function createRoleScope(base, sessionRole, blocked) {
5865
+ function createRoleScope(base2, sessionRole, blocked) {
5858
5866
  let perMessage;
5859
5867
  const current = () => perMessage ?? sessionRole;
5860
- const scoped = () => roleScopedProvider(base, current(), blocked);
5868
+ const scoped = () => roleScopedProvider(base2, current(), blocked);
5861
5869
  return {
5862
5870
  provider: {
5863
5871
  list: () => scoped().list(),
@@ -5875,9 +5883,9 @@ function createRoleScope(base, sessionRole, blocked) {
5875
5883
  }
5876
5884
  };
5877
5885
  }
5878
- function unknownRoleTools(base, role, blocked) {
5886
+ function unknownRoleTools(base2, role, blocked) {
5879
5887
  if (!role?.tools) return [];
5880
- const available2 = new Set(base.list().map((t81) => t81.name));
5888
+ const available2 = new Set(base2.list().map((t81) => t81.name));
5881
5889
  const blockedSet = new Set(blocked);
5882
5890
  return role.tools.filter((name) => !available2.has(name) && !blockedSet.has(name));
5883
5891
  }
@@ -5965,23 +5973,23 @@ function loadCommands(files, source, prefix) {
5965
5973
  }
5966
5974
  function loadCommandFile(file, source, prefix) {
5967
5975
  const at = file.relPath;
5968
- const base = commandNameFromRelPath(file.relPath);
5969
- if (!isValidCommandName(base)) {
5976
+ const base2 = commandNameFromRelPath(file.relPath);
5977
+ if (!isValidCommandName(base2)) {
5970
5978
  return {
5971
5979
  issues: [
5972
5980
  {
5973
5981
  path: at,
5974
- message: t("custom_command.bad_name", { name: base })
5982
+ message: t("custom_command.bad_name", { name: base2 })
5975
5983
  }
5976
5984
  ]
5977
5985
  };
5978
5986
  }
5979
- if (!prefix && RESERVED_COMMAND_NAMES.includes(base)) {
5987
+ if (!prefix && RESERVED_COMMAND_NAMES.includes(base2)) {
5980
5988
  return {
5981
- issues: [{ path: at, message: t("custom_command.reserved", { name: base }) }]
5989
+ issues: [{ path: at, message: t("custom_command.reserved", { name: base2 }) }]
5982
5990
  };
5983
5991
  }
5984
- const name = prefix ? `${prefix}:${base}` : base;
5992
+ const name = prefix ? `${prefix}:${base2}` : base2;
5985
5993
  let raw;
5986
5994
  try {
5987
5995
  raw = readFileSync(file.path, "utf8");
@@ -6209,14 +6217,14 @@ function sortByName(commands) {
6209
6217
  return [...commands].sort((a, b) => a.name.localeCompare(b.name));
6210
6218
  }
6211
6219
  // src/extensions/tool-scope.ts
6212
- function createToolScope(base) {
6220
+ function createToolScope(base2) {
6213
6221
  let allow;
6214
6222
  let names;
6215
6223
  const permitted = (name) => allow ? allow.has(name) : true;
6216
6224
  return {
6217
6225
  provider: {
6218
- list: () => base.list().filter((t81) => permitted(t81.name)),
6219
- get: (name) => permitted(name) ? base.get(name) : void 0
6226
+ list: () => base2.list().filter((t81) => permitted(t81.name)),
6227
+ get: (name) => permitted(name) ? base2.get(name) : void 0
6220
6228
  },
6221
6229
  enter: (next) => {
6222
6230
  const prevAllow = allow;
@@ -6608,11 +6616,11 @@ function expandToken(token) {
6608
6616
  const tilde = token.startsWith("~");
6609
6617
  const body2 = caret || tilde ? token.slice(1) : token;
6610
6618
  if (caret || tilde) {
6611
- const base = parseVersion(body2);
6612
- if (!base) return null;
6619
+ const base2 = parseVersion(body2);
6620
+ if (!base2) return null;
6613
6621
  return [
6614
- { op: ">=", version: base },
6615
- { op: "<", version: caret ? caretUpper(base) : tildeUpper(base) }
6622
+ { op: ">=", version: base2 },
6623
+ { op: "<", version: caret ? caretUpper(base2) : tildeUpper(base2) }
6616
6624
  ];
6617
6625
  }
6618
6626
  const match = /^(>=|<=|>|<|=)?\s*(.+)$/.exec(body2);
@@ -7634,9 +7642,9 @@ function resolveEntrySource(entry, marketSource) {
7634
7642
  if (rel === "" || rel.split("/").includes("..")) return void 0;
7635
7643
  const market = marketSource.trim();
7636
7644
  if (market.startsWith("github:") || market.startsWith("git+")) {
7637
- const [base = "", fragment] = market.split("#", 2);
7645
+ const [base2 = "", fragment] = market.split("#", 2);
7638
7646
  const prefix = fragment?.startsWith("subdir=") ? `${fragment.slice("subdir=".length)}/` : "";
7639
- return `${base}#subdir=${prefix}${rel}`;
7647
+ return `${base2}#subdir=${prefix}${rel}`;
7640
7648
  }
7641
7649
  if (market.startsWith("https://") || market.startsWith("http://")) {
7642
7650
  return `${market.replace(/\/[^/]*$/, "")}/${rel}`;
@@ -9627,7 +9635,7 @@ function admitArtifact(raw, policy) {
9627
9635
  const dims = size ? ` ${size.width}\xD7${size.height}` : "";
9628
9636
  const head = raw.note ? `${raw.note} ` : "";
9629
9637
  const reason9 = rejectionReason(raw, policy, byteLen, size);
9630
- const base = {
9638
+ const base2 = {
9631
9639
  kind: raw.kind,
9632
9640
  mediaType: raw.mediaType,
9633
9641
  bytes: byteLen,
@@ -9636,13 +9644,13 @@ function admitArtifact(raw, policy) {
9636
9644
  };
9637
9645
  if (reason9) {
9638
9646
  return {
9639
- ...base,
9647
+ ...base2,
9640
9648
  summary: `${head}[${raw.mediaType}${dims}\uFF0C${formatBytes(byteLen)}] \u672A\u8FDB\u5165\u4E0A\u4E0B\u6587\uFF1A${reason9}${storedAt(path)}\u3002\u8BF7\u636E\u6B64\u544A\u77E5\u7528\u6237\uFF0C\u5FC5\u8981\u65F6\u8BA9\u7528\u6237\u63D0\u4F9B\u66F4\u5C0F\u7684\u56FE\u3002`
9641
9649
  };
9642
9650
  }
9643
9651
  const tokens = size ? estimateImageTokens(size.width, size.height) : void 0;
9644
9652
  return {
9645
- ...base,
9653
+ ...base2,
9646
9654
  data: raw.base64,
9647
9655
  summary: `${head}[${raw.mediaType}${dims}\uFF0C${formatBytes(byteLen)}]${storedAt(path)}`,
9648
9656
  ...tokens === void 0 ? {} : { estimatedTokens: tokens }
@@ -10016,8 +10024,8 @@ function isReachable(real, state) {
10016
10024
  if (within(state.rootReal, real) || within(state.epochHomeReal, real)) return true;
10017
10025
  return state.allowExternal(real);
10018
10026
  }
10019
- function within(base, target) {
10020
- const rel = relative(base, target);
10027
+ function within(base2, target) {
10028
+ const rel = relative(base2, target);
10021
10029
  return !rel.startsWith("..") && !isAbsolute(rel);
10022
10030
  }
10023
10031
  function resolveSpec(spec, fromFile) {
@@ -10795,13 +10803,54 @@ function classifyProviderError(err) {
10795
10803
  }
10796
10804
  return { code: 9999, recovery: "retry" };
10797
10805
  }
10798
- function toModelMessages(messages) {
10799
- return messages.map(toModelMessage);
10806
+ function toModelMessages(messages, opts = {}) {
10807
+ if (opts.toolMediaInline !== false) return messages.map(toModelMessage);
10808
+ return hoistToolMedia(messages);
10809
+ }
10810
+ function hoistToolMedia(messages) {
10811
+ const out = [];
10812
+ let pending = [];
10813
+ for (const [i, m] of messages.entries()) {
10814
+ if (m.role !== "tool") {
10815
+ out.push(toModelMessage(m));
10816
+ continue;
10817
+ }
10818
+ const results = (m.toolResults ?? []).map((r) => {
10819
+ const inline = inlineArtifacts(r);
10820
+ if (inline.length === 0) return r;
10821
+ for (const artifact of inline) {
10822
+ pending.push({ label: `${r.toolName} (${r.toolCallId})`, artifact });
10823
+ }
10824
+ const note = `(${inline.length} image(s) from this tool call are attached in the next message.)`;
10825
+ return {
10826
+ ...r,
10827
+ output: r.output ? `${r.output}
10828
+ ${note}` : note,
10829
+ artifacts: (r.artifacts ?? []).map(({ data: _data, ...rest }) => rest)
10830
+ };
10831
+ });
10832
+ out.push(toModelMessage({ ...m, toolResults: results }));
10833
+ if (pending.length > 0 && messages[i + 1]?.role !== "tool") {
10834
+ out.push({
10835
+ role: "user",
10836
+ content: [
10837
+ { type: "text", text: "Images returned by the tool call(s) above:" },
10838
+ ...pending.flatMap(({ label, artifact }) => [
10839
+ { type: "text", text: label },
10840
+ { type: "file", mediaType: artifact.mediaType, data: artifact.data }
10841
+ ])
10842
+ ]
10843
+ });
10844
+ pending = [];
10845
+ }
10846
+ }
10847
+ return out;
10848
+ }
10849
+ function inlineArtifacts(r) {
10850
+ return (r.artifacts ?? []).filter((a) => Boolean(a.data));
10800
10851
  }
10801
10852
  function toToolOutput(r) {
10802
- const inline = (r.artifacts ?? []).filter(
10803
- (a) => Boolean(a.data)
10804
- );
10853
+ const inline = inlineArtifacts(r);
10805
10854
  if (inline.length === 0) return { type: "text", value: r.output };
10806
10855
  return {
10807
10856
  type: "content",
@@ -11570,17 +11619,25 @@ function resolveArtifactRetention(partial) {
11570
11619
  function measure(dir) {
11571
11620
  let bytes = 0;
11572
11621
  let mtimeMs = 0;
11573
- try {
11574
- for (const name of readdirSync(dir)) {
11622
+ const pending = [dir];
11623
+ while (pending.length > 0) {
11624
+ const at = pending.pop();
11625
+ let names;
11626
+ try {
11627
+ names = readdirSync(at);
11628
+ } catch {
11629
+ continue;
11630
+ }
11631
+ for (const name of names) {
11575
11632
  try {
11576
- const st = statSync(join(dir, name));
11633
+ const st = lstatSync(join(at, name));
11634
+ if (st.isDirectory()) pending.push(join(at, name));
11577
11635
  if (!st.isFile()) continue;
11578
11636
  bytes += st.size;
11579
11637
  mtimeMs = Math.max(mtimeMs, st.mtimeMs);
11580
11638
  } catch {
11581
11639
  }
11582
11640
  }
11583
- } catch {
11584
11641
  }
11585
11642
  return { path: dir, bytes, mtimeMs };
11586
11643
  }
@@ -11834,7 +11891,7 @@ var ToolExecutor = class {
11834
11891
  pruned = false;
11835
11892
  async execute(tc, sessionId, signal, parentSpan, onOutput, depth = 0) {
11836
11893
  const et = this.toolFor(tc.toolName, depth);
11837
- const base = { toolCallId: tc.toolCallId, toolName: tc.toolName, args: tc.input };
11894
+ const base2 = { toolCallId: tc.toolCallId, toolName: tc.toolName, args: tc.input };
11838
11895
  const telemetry = this.deps.telemetry ?? NOOP_TELEMETRY;
11839
11896
  const startedAt = Date.now();
11840
11897
  const span = telemetry.startSpan(
@@ -11862,7 +11919,7 @@ var ToolExecutor = class {
11862
11919
  if (!et) {
11863
11920
  const declared = this.codeModeDeclared(tc.toolName);
11864
11921
  return finish({
11865
- ...base,
11922
+ ...base2,
11866
11923
  success: false,
11867
11924
  output: declared ? codeModeDeclaredNotice(tc.toolName) : `\u5DE5\u5177 ${tc.toolName} \u5F53\u524D\u4E0D\u53EF\u7528\uFF08\u53EF\u80FD\u5DF2\u88AB MCP server \u64A4\u4E0B\uFF09\u3002\u8BF7\u7528\u5F53\u524D\u5DE5\u5177\u8868\u91CC\u7684\u5DE5\u5177\u91CD\u8BD5\u3002`
11868
11925
  });
@@ -11881,14 +11938,14 @@ var ToolExecutor = class {
11881
11938
  );
11882
11939
  if (blocked?.blocked) {
11883
11940
  return finish({
11884
- ...base,
11941
+ ...base2,
11885
11942
  success: false,
11886
11943
  output: `\u64CD\u4F5C\u88AB hook \u963B\u6B62: ${blocked.reason || "\u672A\u77E5\u539F\u56E0"}`
11887
11944
  });
11888
11945
  }
11889
11946
  const gate = await this.checkPermission(et, tc.input, signal);
11890
11947
  if (!gate.allowed) {
11891
- return finish({ ...base, success: false, output: gate.output });
11948
+ return finish({ ...base2, success: false, output: gate.output });
11892
11949
  }
11893
11950
  const targets = this.writeTargets(et, tc.input);
11894
11951
  const snapshotted = await this.snapshot(sessionId, targets);
@@ -11933,7 +11990,7 @@ var ToolExecutor = class {
11933
11990
  const artifacts = r.artifacts ? admitArtifacts(r.artifacts, this.policy(sessionId)) : [];
11934
11991
  const diffs = before ? await collectDiffs(before, this.deps.workDir()) : [];
11935
11992
  return finish({
11936
- ...base,
11993
+ ...base2,
11937
11994
  success: true,
11938
11995
  output: this.renderOutput(
11939
11996
  et,
@@ -11944,7 +12001,7 @@ var ToolExecutor = class {
11944
12001
  });
11945
12002
  }
11946
12003
  return finish({
11947
- ...base,
12004
+ ...base2,
11948
12005
  success: false,
11949
12006
  output: this.renderOutput(
11950
12007
  et,
@@ -11954,7 +12011,7 @@ var ToolExecutor = class {
11954
12011
  } catch (err) {
11955
12012
  span.recordError(err);
11956
12013
  return finish({
11957
- ...base,
12014
+ ...base2,
11958
12015
  success: false,
11959
12016
  output: `\u5DE5\u5177\u6267\u884C\u5F02\u5E38: ${err instanceof Error ? err.message : String(err)}`
11960
12017
  });
@@ -12009,10 +12066,10 @@ var ToolExecutor = class {
12009
12066
  return text;
12010
12067
  }
12011
12068
  policy(sessionId) {
12012
- const base = this.deps.multimodal?.() ?? resolveMultimodalPolicy();
12013
- if (base.artifactsDir) return base;
12014
- this.pruneOnce(base.homeDir);
12015
- return { ...base, artifactsDir: artifactsDir(sessionId, base.homeDir) };
12069
+ const base2 = this.deps.multimodal?.() ?? resolveMultimodalPolicy();
12070
+ if (base2.artifactsDir) return base2;
12071
+ this.pruneOnce(base2.homeDir);
12072
+ return { ...base2, artifactsDir: artifactsDir(sessionId, base2.homeDir) };
12016
12073
  }
12017
12074
  pruneOnce(homeDir) {
12018
12075
  if (this.pruned) return;
@@ -13013,16 +13070,147 @@ function readVersion(raw, issues) {
13013
13070
  function isPlainObject(value) {
13014
13071
  return typeof value === "object" && value !== null && !Array.isArray(value);
13015
13072
  }
13016
- var CredentialSchema = z.object({
13017
- apiKey: z.string(),
13018
- model: z.string().optional()
13019
- });
13020
- var ProviderSchema = z.object({
13021
- type: z.enum(PROVIDER_TYPES).default("openai"),
13022
- apiKey: z.string().default(""),
13023
- credentials: z.array(CredentialSchema).default(() => []),
13024
- baseUrl: z.string().optional()
13073
+ var MODEL_ENTRIES_FILE = "models.json";
13074
+ var FORMAT_VERSION = 1;
13075
+ function modelEntriesPath(homeDir) {
13076
+ return join(homeDir, MODEL_ENTRIES_FILE);
13077
+ }
13078
+ var positiveInt = z.int().positive();
13079
+ var ModelEntrySchema = z.object({
13080
+ id: z.string().refine(isModelEntryId, { message: "invalid id" }),
13081
+ name: z.string().optional(),
13082
+ provider: z.enum(PROVIDER_TYPES),
13083
+ protocol: z.enum(MODEL_ENTRY_PROTOCOLS).optional(),
13084
+ baseUrl: z.url({ protocol: /^https?$/ }).optional(),
13085
+ rawUrl: z.boolean().optional(),
13086
+ model: z.string().trim().min(1),
13087
+ capabilities: z.object({
13088
+ tools: z.boolean().optional(),
13089
+ images: z.boolean().optional(),
13090
+ reasoning: z.boolean().optional(),
13091
+ reasoningOnly: z.boolean().optional(),
13092
+ reasoningToggle: z.boolean().optional()
13093
+ }).optional(),
13094
+ reasoning: z.object({
13095
+ levels: z.array(z.enum(REASONING_EFFORTS)),
13096
+ default: z.enum(REASONING_EFFORTS).optional()
13097
+ }).optional(),
13098
+ contextLength: positiveInt.optional(),
13099
+ maxOutputTokens: positiveInt.optional(),
13100
+ host: z.literal(true).optional()
13025
13101
  });
13102
+ function normalizeModelEntry(entry) {
13103
+ const custom = entry.provider === "openai-compatible";
13104
+ const protocol = custom ? entry.protocol ?? "openai-chat" : void 0;
13105
+ const out = { id: entry.id, provider: entry.provider, model: entry.model.trim() };
13106
+ const name = entry.name?.trim();
13107
+ if (name) out.name = name;
13108
+ if (protocol) out.protocol = protocol;
13109
+ const rawUrl = Boolean(entry.rawUrl && protocol === "openai-chat" && entry.baseUrl?.trim());
13110
+ const baseUrl = entry.baseUrl?.trim();
13111
+ if (baseUrl) out.baseUrl = rawUrl ? baseUrl : toBaseUrl(baseUrl, entry);
13112
+ if (rawUrl) out.rawUrl = true;
13113
+ const caps = entry.capabilities;
13114
+ if (caps && Object.values(caps).some((v) => v !== void 0)) {
13115
+ out.capabilities = Object.fromEntries(
13116
+ Object.entries(caps).filter(([, v]) => v !== void 0)
13117
+ );
13118
+ }
13119
+ if (entry.reasoning) {
13120
+ const levels = REASONING_EFFORTS.filter((l) => entry.reasoning?.levels.includes(l));
13121
+ if (levels.length > 0) {
13122
+ const def = entry.reasoning.default;
13123
+ out.reasoning = def && levels.includes(def) ? { levels, default: def } : { levels };
13124
+ }
13125
+ }
13126
+ if (entry.contextLength) out.contextLength = entry.contextLength;
13127
+ if (entry.maxOutputTokens) out.maxOutputTokens = entry.maxOutputTokens;
13128
+ if (entry.host) out.host = true;
13129
+ return out;
13130
+ }
13131
+ function toBaseUrl(url, entry) {
13132
+ const anthropic = entry.provider === "anthropic" || entry.provider === "openai-compatible" && entry.protocol === "anthropic-messages";
13133
+ const suffix = anthropic ? /\/messages\/?$/ : /\/chat\/completions\/?$/;
13134
+ return url.replace(suffix, "").replace(/\/+$/, "");
13135
+ }
13136
+ function parseModelEntry(raw) {
13137
+ const parsed = ModelEntrySchema.safeParse(raw);
13138
+ if (!parsed.success) {
13139
+ const first2 = parsed.error.issues[0];
13140
+ return {
13141
+ ok: false,
13142
+ reason: first2 ? `${first2.path.join(".") || "(root)"}: ${first2.message}` : ""
13143
+ };
13144
+ }
13145
+ return { ok: true, entry: normalizeModelEntry(parsed.data) };
13146
+ }
13147
+ function loadModelEntries(homeDir) {
13148
+ const path = modelEntriesPath(homeDir);
13149
+ if (!existsSync(path)) return { entries: [], issues: [] };
13150
+ const hostLabelOf = (raw2) => {
13151
+ const label = raw2?.hostLabel;
13152
+ return typeof label === "string" && label.trim() !== "" ? label.trim() : void 0;
13153
+ };
13154
+ let raw;
13155
+ try {
13156
+ raw = JSON.parse(readFileSync(path, "utf-8"));
13157
+ } catch (err) {
13158
+ return {
13159
+ entries: [],
13160
+ issues: [`${path}: ${err instanceof Error ? err.message : String(err)}`]
13161
+ };
13162
+ }
13163
+ const list = raw?.models;
13164
+ if (!Array.isArray(list)) {
13165
+ return { entries: [], issues: [`${path}: "models" is not an array`] };
13166
+ }
13167
+ const hostLabel = hostLabelOf(raw);
13168
+ const entries2 = [];
13169
+ const issues = [];
13170
+ const seen = /* @__PURE__ */ new Set();
13171
+ list.forEach((item, i) => {
13172
+ const parsed = parseModelEntry(item);
13173
+ if (!parsed.ok) {
13174
+ issues.push(`${path} models[${i}]: ${parsed.reason}`);
13175
+ return;
13176
+ }
13177
+ if (seen.has(parsed.entry.id)) {
13178
+ issues.push(`${path} models[${i}]: duplicate id "${parsed.entry.id}"`);
13179
+ return;
13180
+ }
13181
+ seen.add(parsed.entry.id);
13182
+ entries2.push(parsed.entry);
13183
+ });
13184
+ return hostLabel ? { entries: entries2, issues, hostLabel } : { entries: entries2, issues };
13185
+ }
13186
+ function saveModelEntries(homeDir, entries2, hostLabel) {
13187
+ const path = modelEntriesPath(homeDir);
13188
+ if (loadModelEntries(homeDir).issues.length > 0) {
13189
+ const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13190
+ copyFileSync(path, `${path}.corrupt.${ts}.bak`);
13191
+ }
13192
+ const label = hostLabel !== void 0 ? hostLabel : loadModelEntries(homeDir).hostLabel;
13193
+ const body2 = `${JSON.stringify(
13194
+ {
13195
+ version: FORMAT_VERSION,
13196
+ ...label ? { hostLabel: label } : {},
13197
+ models: entries2.map(normalizeModelEntry)
13198
+ },
13199
+ null,
13200
+ 2
13201
+ )}
13202
+ `;
13203
+ const tmp = `${path}.${process.pid}.tmp`;
13204
+ mkdirSync(dirname(path), { recursive: true });
13205
+ try {
13206
+ writeFileSync(tmp, body2, { encoding: "utf-8", mode: 384 });
13207
+ renameSync(tmp, path);
13208
+ } catch (err) {
13209
+ rmSync(tmp, { force: true });
13210
+ throw err;
13211
+ }
13212
+ }
13213
+ var EntryRefSchema = z.string().refine((v) => parseModelRef(v) !== void 0, { message: "expected @<model-entry-id>" });
13026
13214
  var RetrySchema = z.object({
13027
13215
  maxRetries: z.int().min(0).default(3),
13028
13216
  baseBackoffMs: z.int().min(0).default(1e3),
@@ -13145,9 +13333,9 @@ var TelemetrySchema = z.object({
13145
13333
  endpoint: z.string().optional()
13146
13334
  });
13147
13335
  var EpochConfigSchema = z.object({
13148
- provider: ProviderSchema.prefault({}),
13149
- model: z.string().min(1).default("gpt-4o-mini"),
13150
- models: z.object({ utility: z.string().min(1).optional() }).optional(),
13336
+ model: EntryRefSchema.optional(),
13337
+ models: z.object({ utility: EntryRefSchema.optional() }).optional(),
13338
+ modelEntries: z.array(ModelEntrySchema).optional(),
13151
13339
  shell: z.enum(SHELL_KINDS).optional(),
13152
13340
  maxTurns: z.int().nonnegative().default(DEFAULT_MAX_TURNS),
13153
13341
  contextLength: z.int().positive().default(2e5),
@@ -13163,10 +13351,7 @@ var EpochConfigSchema = z.object({
13163
13351
  logLevel: z.enum(["debug", "info", "warn", "error"]).default("info"),
13164
13352
  profile: z.string().min(1).default("default"),
13165
13353
  retry: RetrySchema.prefault({}),
13166
- fallback: z.object({
13167
- providers: z.array(z.enum(PROVIDER_TYPES)).default([]),
13168
- model: z.string().min(1).optional()
13169
- }).optional(),
13354
+ fallback: z.object({ model: EntryRefSchema.optional() }).optional(),
13170
13355
  hooks: z.record(z.string(), z.unknown()).optional(),
13171
13356
  trust: TrustSchema.optional(),
13172
13357
  sandbox: SandboxSchema.prefault({}),
@@ -13186,13 +13371,16 @@ var EpochConfigSchema = z.object({
13186
13371
  statusLine: StatusLineSchema.optional(),
13187
13372
  display: DisplaySchema2.optional(),
13188
13373
  brand: z.string().min(1).optional(),
13374
+ brandWhenSidebarCollapsed: z.enum(BRAND_WHEN_SIDEBAR_COLLAPSED).optional(),
13375
+ brandMenuId: z.string().min(1).optional(),
13189
13376
  sidebarMenu: z.array(SidebarNavItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13190
13377
  message: "sidebarMenu: id must be unique"
13191
13378
  }).refine((items) => items.filter((item) => item.primary === true).length <= 1, {
13192
13379
  message: "sidebarMenu: at most one item can be primary"
13193
13380
  }).optional(),
13194
- userMenu: z.array(SidebarMenuItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13195
- message: "userMenu: id must be unique"
13381
+ userMenuHost: z.boolean().optional(),
13382
+ sidebarHeadMenu: z.array(SidebarMenuItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13383
+ message: "sidebarHeadMenu: id must be unique"
13196
13384
  }).optional(),
13197
13385
  blankCards: z.array(BlankCardSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13198
13386
  message: "blankCards: id must be unique"
@@ -13276,7 +13464,7 @@ function describeJsonError(err, text) {
13276
13464
  const reason9 = message.replace(/\s*in JSON at position \d+.*$/, "");
13277
13465
  return t("config_load.json_at", { reason: reason9, line, column });
13278
13466
  }
13279
- function readSettingsFile(path, schema, known, empty, issues) {
13467
+ function readSettingsFile(path, schema, known, empty, issues, sanitize = (raw) => raw) {
13280
13468
  if (!existsSync(path)) return void 0;
13281
13469
  const text = readFileSync(path, "utf-8");
13282
13470
  let raw;
@@ -13287,11 +13475,27 @@ function readSettingsFile(path, schema, known, empty, issues) {
13287
13475
  issues.push({ path, message: t("config_load.json_invalid", { reason: reason9 }) });
13288
13476
  return { failed: reason9 };
13289
13477
  }
13290
- issues.push(...atFile(path, unknownKeyIssues(known, raw)));
13291
- const parsed = parseLenient(schema, raw, empty);
13478
+ const cleaned = sanitize(raw, issues);
13479
+ issues.push(...atFile(path, unknownKeyIssues(known, cleaned)));
13480
+ const parsed = parseLenient(schema, cleaned, empty);
13292
13481
  issues.push(...atFile(path, parsed.issues));
13293
13482
  return { file: parsed.value };
13294
13483
  }
13484
+ function stripLegacyModelValue(raw, issues) {
13485
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
13486
+ const out = { ...raw };
13487
+ const value = out["model"];
13488
+ if (value === void 0 || typeof value === "string" && parseModelRef(value)) return out;
13489
+ delete out["model"];
13490
+ issues.push({
13491
+ path: "model",
13492
+ message: t("config_load.legacy_ignored", {
13493
+ key: "model",
13494
+ hint: t("config_load.legacy_hint_main")
13495
+ })
13496
+ });
13497
+ return out;
13498
+ }
13295
13499
  // src/config/managed.ts
13296
13500
  var ManagedSettingsSchema = SettingsFileSchema.extend({
13297
13501
  allowManagedPermissionRulesOnly: z.boolean().optional(),
@@ -13317,7 +13521,8 @@ function readManagedSettings(path, issues) {
13317
13521
  ManagedSettingsSchema,
13318
13522
  MANAGED_KEYS,
13319
13523
  {},
13320
- issues
13524
+ issues,
13525
+ stripLegacyModelValue
13321
13526
  );
13322
13527
  if (!read || "failed" in read) return { policy: noManagedPolicy(path), read };
13323
13528
  return { policy: policyOf(path, read.file), read };
@@ -13377,11 +13582,11 @@ function countRules(lists) {
13377
13582
  if (!lists) return 0;
13378
13583
  return BUCKETS2.reduce((sum, b) => sum + (lists[b]?.length ?? 0), 0);
13379
13584
  }
13380
- function mergeLists(base, add) {
13585
+ function mergeLists(base2, add) {
13381
13586
  const out = {};
13382
13587
  for (const bucket of BUCKETS2) {
13383
- const merged = [...base[bucket] ?? [], ...add[bucket] ?? []];
13384
- if (merged.length === 0 && base[bucket] === void 0 && add[bucket] === void 0) continue;
13588
+ const merged = [...base2[bucket] ?? [], ...add[bucket] ?? []];
13589
+ if (merged.length === 0 && base2[bucket] === void 0 && add[bucket] === void 0) continue;
13385
13590
  out[bucket] = [...new Set(merged)];
13386
13591
  }
13387
13592
  return out;
@@ -13412,7 +13617,8 @@ function loadLayer(source, execTrusted, issues) {
13412
13617
  SettingsFileSchema,
13413
13618
  SETTINGS_KEYS,
13414
13619
  {},
13415
- issues
13620
+ issues,
13621
+ stripLegacyModelValue
13416
13622
  );
13417
13623
  return finishLayer(source, read, execTrusted, issues);
13418
13624
  }
@@ -13455,8 +13661,8 @@ function describe6(layer, gated, lists) {
13455
13661
  const parts = BUCKETS2.map((b) => `${b} ${lists[b]?.length ?? 0}`).join(" / ");
13456
13662
  return t("config_load.loaded", { label, parts });
13457
13663
  }
13458
- function resolveSettings(base, projectRoot, execTrusted, opts = {}) {
13459
- const config = structuredClone(base);
13664
+ function resolveSettings(base2, projectRoot, execTrusted, opts = {}) {
13665
+ const config = structuredClone(base2);
13460
13666
  const issues = [];
13461
13667
  const layers = [];
13462
13668
  let lists = {};
@@ -13485,12 +13691,19 @@ function resolveSettings(base, projectRoot, execTrusted, opts = {}) {
13485
13691
  lists = managed.policy.allowManagedPermissionRulesOnly ? managed.outcome.lists : mergeLists(lists, managed.outcome.lists);
13486
13692
  layers.push({ ...managed.outcome, scalars: applyScalars(config, managed.file) });
13487
13693
  }
13488
- if (countRules(lists) > 0 || base.permissions !== void 0) config.permissions = lists;
13694
+ if (countRules(lists) > 0 || base2.permissions !== void 0) config.permissions = lists;
13489
13695
  return { config, layers, issues, managed: managed.policy };
13490
13696
  }
13491
13697
  function loadPluginLayer(entry, issues) {
13492
13698
  const { plugin, path } = entry;
13493
- const read = readSettingsFile(path, SettingsFileSchema, SETTINGS_KEYS, {}, issues);
13699
+ const read = readSettingsFile(
13700
+ path,
13701
+ SettingsFileSchema,
13702
+ SETTINGS_KEYS,
13703
+ {},
13704
+ issues,
13705
+ stripLegacyModelValue
13706
+ );
13494
13707
  const label = t("config_load.layer_plugin_named", { plugin });
13495
13708
  if (!read) return { layer: "plugin", path, status: "missing", lists: {}, scalars: {} };
13496
13709
  if ("failed" in read) {
@@ -13566,11 +13779,11 @@ function originOf(origins, key) {
13566
13779
  if (origins.file.has(key)) return "user";
13567
13780
  return "builtin";
13568
13781
  }
13569
- function explainSetting(key, base, file) {
13570
- const baseLayer = baseLayerOf(base);
13782
+ function explainSetting(key, base2, file) {
13783
+ const baseLayer = baseLayerOf(base2);
13571
13784
  const steps = [
13572
- step("builtin", base.builtin),
13573
- baseLayer === "builtin" ? { layer: "user" } : step(baseLayer, base.value)
13785
+ step("builtin", base2.builtin),
13786
+ baseLayer === "builtin" ? { layer: "user" } : step(baseLayer, base2.value)
13574
13787
  ];
13575
13788
  if (file) {
13576
13789
  for (const layer of SETTINGS_LAYER_ORDER) {
@@ -13587,10 +13800,10 @@ function explainSetting(key, base, file) {
13587
13800
  chain: file ? steps : []
13588
13801
  };
13589
13802
  }
13590
- function baseLayerOf(base) {
13591
- if (base.origin === "env") return "env";
13592
- if (base.origin === "user") return "user";
13593
- return sameValue(base.value, base.builtin) ? "builtin" : "unknown";
13803
+ function baseLayerOf(base2) {
13804
+ if (base2.origin === "env") return "env";
13805
+ if (base2.origin === "user") return "user";
13806
+ return sameValue(base2.value, base2.builtin) ? "builtin" : "unknown";
13594
13807
  }
13595
13808
  function valueOf(file, layer) {
13596
13809
  return file.layers.find((l) => l.layer === layer)?.scalars[file.key];
@@ -13640,8 +13853,9 @@ function _loadImpl(overrideHomeDir) {
13640
13853
  mkdirSync(homeDir, { recursive: true });
13641
13854
  const configPath2 = getConfigPath(homeDir);
13642
13855
  const { mtime, size } = fileStamp(configPath2);
13856
+ const entriesStamp = stampKey(modelEntriesPath(homeDir));
13643
13857
  const envSnapshot = snapshotEnv();
13644
- if (_cache && _cache.homeDir === homeDir && _cache.mtime === mtime && _cache.size === size && sameEnv(_cache.envSnapshot, envSnapshot)) {
13858
+ if (_cache && _cache.homeDir === homeDir && _cache.mtime === mtime && _cache.size === size && _cache.entriesStamp === entriesStamp && sameEnv(_cache.envSnapshot, envSnapshot)) {
13645
13859
  _lastIssues = _cache.issues;
13646
13860
  _lastOrigins = _cache.origins;
13647
13861
  return _cache.config;
@@ -13657,6 +13871,7 @@ function _loadImpl(overrideHomeDir) {
13657
13871
  homeDir,
13658
13872
  mtime,
13659
13873
  size,
13874
+ entriesStamp,
13660
13875
  envSnapshot,
13661
13876
  config: cached.config,
13662
13877
  issues,
@@ -13665,12 +13880,9 @@ function _loadImpl(overrideHomeDir) {
13665
13880
  return cached.config;
13666
13881
  }
13667
13882
  const config = parseRawConfig(read.raw, issues, track);
13668
- const override = readProviderOverride(issues);
13669
- const pinned = applyProviderOverride(config, track, override);
13670
- const secretSource = applySecretsFromEnv(config, homeDir, track);
13671
- if (pinned) applyProviderOverride(config, track, override);
13883
+ reportDroppedEnv(issues);
13672
13884
  applyEnvOverrides(config, issues, track);
13673
- if (secretSource === "config-file" && !pinned) detectProviderFromKey(config);
13885
+ applyModelEntries(config, homeDir, issues);
13674
13886
  if (overrideHomeDir) {
13675
13887
  config.homeDir = overrideHomeDir;
13676
13888
  config.dbPath = dbPath(overrideHomeDir);
@@ -13679,7 +13891,16 @@ function _loadImpl(overrideHomeDir) {
13679
13891
  _lastIssues = issues;
13680
13892
  _lastOrigins = origins;
13681
13893
  _lastKnownGood = { homeDir, config: structuredClone(config), origins };
13682
- _cache = { homeDir, mtime, size, envSnapshot, config: structuredClone(config), issues, origins };
13894
+ _cache = {
13895
+ homeDir,
13896
+ mtime,
13897
+ size,
13898
+ entriesStamp,
13899
+ envSnapshot,
13900
+ config: structuredClone(config),
13901
+ issues,
13902
+ origins
13903
+ };
13683
13904
  return config;
13684
13905
  }
13685
13906
  function readRawConfig(configPath2, issues) {
@@ -13705,106 +13926,122 @@ function parseRawConfig(raw, issues, track) {
13705
13926
  const expanded = expandEnvVars(raw);
13706
13927
  const migrated = migrateRawConfig(expanded);
13707
13928
  issues.push(...migrated.issues);
13708
- issues.push(...unknownKeyIssues(CONFIG_KEYS, migrated.raw));
13709
- const outcome = parseLenient(
13710
- EpochConfigSchema,
13711
- dropEmptySections(migrated.raw),
13712
- EpochConfigSchema.parse({})
13713
- );
13929
+ const legacyStripped = stripLegacyModelConfig(migrated.raw, issues);
13930
+ issues.push(...unknownKeyIssues(CONFIG_KEYS, legacyStripped));
13931
+ const cleaned = dropEmptySections(legacyStripped);
13932
+ const outcome = parseLenient(EpochConfigSchema, cleaned, EpochConfigSchema.parse({}));
13714
13933
  issues.push(...outcome.issues);
13715
13934
  issues.push(...settingsHiddenIssues(outcome.value));
13716
13935
  issues.push(...settingsPaneBeforeIssues(outcome.value));
13717
- if (outcome.issues.length === 0) collectKeyPaths(migrated.raw, track.file);
13936
+ if (outcome.issues.length === 0) collectKeyPaths(cleaned, track.file);
13718
13937
  return outcome.value;
13719
13938
  }
13939
+ function stripLegacyModelConfig(raw, issues) {
13940
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
13941
+ const out = { ...raw };
13942
+ const drop = (path, hintKey, messageKey = "config_load.legacy_ignored") => {
13943
+ issues.push({
13944
+ path,
13945
+ message: t(messageKey, { key: path, hint: t(hintKey) })
13946
+ });
13947
+ };
13948
+ for (const key of ["provider", "gateway"]) {
13949
+ if (key in out) {
13950
+ delete out[key];
13951
+ drop(key, "config_load.legacy_hint_endpoint");
13952
+ }
13953
+ }
13954
+ const notEntry = (v) => v !== void 0 && (typeof v !== "string" || parseModelRef(v) === void 0);
13955
+ const dropModel = (path, hintKey, value) => {
13956
+ if (typeof value === "string" && value.trimStart().startsWith(ENTRY_REF_PREFIX))
13957
+ drop(path, hintKey, "config_load.entry_ref_invalid");
13958
+ else drop(path, hintKey);
13959
+ };
13960
+ if (notEntry(out.model)) {
13961
+ const raw2 = out.model;
13962
+ delete out.model;
13963
+ dropModel("model", "config_load.legacy_hint_main", raw2);
13964
+ }
13965
+ const section = (key) => {
13966
+ const v = out[key];
13967
+ if (!v || typeof v !== "object" || Array.isArray(v)) return void 0;
13968
+ const copy = { ...v };
13969
+ out[key] = copy;
13970
+ return copy;
13971
+ };
13972
+ const models = section("models");
13973
+ if (models && notEntry(models.utility)) {
13974
+ const raw2 = models.utility;
13975
+ delete models.utility;
13976
+ dropModel("models.utility", "config_load.legacy_hint_utility", raw2);
13977
+ }
13978
+ const fallback2 = section("fallback");
13979
+ if (fallback2 && notEntry(fallback2.model)) {
13980
+ const raw2 = fallback2.model;
13981
+ delete fallback2.model;
13982
+ dropModel("fallback.model", "config_load.legacy_hint_fallback", raw2);
13983
+ }
13984
+ if (fallback2 && "providers" in fallback2) {
13985
+ delete fallback2.providers;
13986
+ drop("fallback.providers", "config_load.legacy_hint_fallback");
13987
+ }
13988
+ return out;
13989
+ }
13720
13990
  function dropEmptySections(raw) {
13721
13991
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
13722
13992
  const entries2 = Object.entries(raw);
13723
13993
  if (!entries2.some(([, v]) => v === null)) return raw;
13724
13994
  return Object.fromEntries(entries2.filter(([, v]) => v !== null));
13725
13995
  }
13726
- function applySecretsFromEnv(config, homeDir, track) {
13727
- const env = readProviderKeyEnv(homeDir);
13728
- const lookup = (name) => env[name];
13729
- const ownVar = apiKeyEnvVar(config.provider.type);
13730
- const raw = ownVar ? lookup(ownVar) : void 0;
13731
- if (raw) return finishSecrets(config, raw, "explicit", track);
13732
- for (const info of PROVIDER_INFOS) {
13733
- if (!info.apiKeyEnv) continue;
13734
- const value = lookup(info.apiKeyEnv);
13735
- if (!value) continue;
13736
- config.provider.type = info.type;
13737
- track.env.add("provider.type");
13738
- return finishSecrets(config, value, "explicit", track);
13739
- }
13740
- return "config-file";
13741
- }
13742
- function readProviderOverride(issues) {
13743
- const override = {};
13744
- const baseUrl = process.env.EPOCH_BASE_URL;
13745
- if (baseUrl) {
13746
- override.baseUrl = baseUrl;
13747
- override.type = "openai-compatible";
13748
- }
13749
- const type = process.env.EPOCH_PROVIDER;
13750
- if (type) {
13751
- if (isProviderType(type)) {
13752
- override.type ??= type;
13753
- } else {
13754
- issues.push(envIssue("EPOCH_PROVIDER", type, PROVIDER_TYPES.join(" / ")));
13755
- }
13996
+ function reportDroppedEnv(issues) {
13997
+ for (const name of ["EPOCH_PROVIDER", "EPOCH_BASE_URL"]) {
13998
+ if (!process.env[name]) continue;
13999
+ issues.push({
14000
+ path: name,
14001
+ message: t("config_load.legacy_ignored", {
14002
+ key: name,
14003
+ hint: t("config_load.legacy_hint_endpoint")
14004
+ })
14005
+ });
13756
14006
  }
13757
- return override;
13758
14007
  }
13759
- function applyProviderOverride(config, track, override) {
13760
- if (override.baseUrl) {
13761
- config.provider.baseUrl = override.baseUrl;
13762
- track.env.add("provider.baseUrl");
13763
- }
13764
- if (!override.type) return false;
13765
- config.provider.type = override.type;
13766
- track.env.add("provider.type");
13767
- return true;
14008
+ function readSecret(name, homeDir) {
14009
+ return process.env[name] || loadEnvFile(getEnvPath(homeDir))[name] || getSecretValues()[name] || void 0;
13768
14010
  }
13769
- function readProviderKeyEnv(homeDir = resolveHomeDir()) {
13770
- const dotEnv = loadEnvFile(getEnvPath(homeDir));
13771
- const fromStore = getSecretValues();
13772
- const result = {};
13773
- for (const name of API_KEY_ENV_VARS) {
13774
- const value = process.env[name] || dotEnv[name] || fromStore[name];
13775
- if (value) result[name] = value;
13776
- }
13777
- return result;
14011
+ function readEntryKey(id, homeDir = resolveHomeDir()) {
14012
+ return readSecret(entrySecretName(id), homeDir);
13778
14013
  }
13779
- function hasProviderCredentials(homeDir = resolveHomeDir()) {
13780
- if (Object.keys(readProviderKeyEnv(homeDir)).length > 0) return true;
13781
- return listStoredSecretNames(homeDir).some((n) => API_KEY_ENV_VARS.includes(n));
14014
+ function hasUsableModel(homeDir = resolveHomeDir()) {
14015
+ if (hasProviderCredentials(homeDir)) return true;
14016
+ return loadModelEntries(homeDir).entries.some(
14017
+ (e) => e.host === true || e.provider === "ollama" || e.provider === "openai-compatible"
14018
+ );
13782
14019
  }
13783
- function finishSecrets(config, raw, source, track) {
13784
- const first2 = raw.split(",")[0]?.trim();
13785
- if (first2) config.provider.apiKey = first2;
13786
- config.provider.credentials = parseKeys(raw);
13787
- track.env.add("provider.apiKey");
13788
- track.env.add("provider.credentials");
13789
- return source;
14020
+ function hasProviderCredentials(homeDir = resolveHomeDir()) {
14021
+ const dotEnv = loadEnvFile(getEnvPath(homeDir));
14022
+ const named = (n) => isEntrySecretName(n) && Boolean(process.env[n] || dotEnv[n]);
14023
+ return Object.keys(process.env).some(named) || Object.keys(dotEnv).some(named) || listStoredSecretNames(homeDir).some(isEntrySecretName);
13790
14024
  }
13791
14025
  function applyEnvOverrides(config, issues, track) {
13792
14026
  const env = process.env;
13793
14027
  if (env.EPOCH_MODEL) {
13794
- config.model = env.EPOCH_MODEL;
13795
- track.env.add("model");
14028
+ if (parseModelRef(env.EPOCH_MODEL)) {
14029
+ config.model = env.EPOCH_MODEL;
14030
+ track.env.add("model");
14031
+ } else issues.push(envIssue("EPOCH_MODEL", env.EPOCH_MODEL, "@<model-entry-id>"));
13796
14032
  }
13797
14033
  if (env.EPOCH_PROFILE) {
13798
14034
  config.profile = env.EPOCH_PROFILE;
13799
14035
  track.env.add("profile");
13800
14036
  }
13801
14037
  if (env.EPOCH_FALLBACK_MODEL) {
13802
- config.fallback = {
13803
- providers: config.fallback?.providers ?? [],
13804
- model: env.EPOCH_FALLBACK_MODEL
13805
- };
13806
- track.env.add("fallback");
13807
- track.env.add("fallback.model");
14038
+ if (parseModelRef(env.EPOCH_FALLBACK_MODEL)) {
14039
+ config.fallback = { model: env.EPOCH_FALLBACK_MODEL };
14040
+ track.env.add("fallback");
14041
+ track.env.add("fallback.model");
14042
+ } else {
14043
+ issues.push(envIssue("EPOCH_FALLBACK_MODEL", env.EPOCH_FALLBACK_MODEL, "@<model-entry-id>"));
14044
+ }
13808
14045
  }
13809
14046
  if (env.EPOCH_MAX_TURNS) {
13810
14047
  const turns = Number(env.EPOCH_MAX_TURNS);
@@ -13846,11 +14083,6 @@ function applyEnvOverrides(config, issues, track) {
13846
14083
  function envIssue(name, value, expected) {
13847
14084
  return { path: name, message: t("config_load.env_expected", { expected, value }) };
13848
14085
  }
13849
- function detectProviderFromKey(config) {
13850
- const key = config.provider.apiKey;
13851
- if (key.startsWith("sk-ant")) config.provider.type = "anthropic";
13852
- else if (key.startsWith("sk-d")) config.provider.type = "deepseek";
13853
- }
13854
14086
  function backupCorruptConfig(path) {
13855
14087
  try {
13856
14088
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -13863,7 +14095,6 @@ function backupCorruptConfig(path) {
13863
14095
  }
13864
14096
  function snapshotEnv() {
13865
14097
  const keys = [
13866
- ...API_KEY_ENV_VARS,
13867
14098
  "EPOCH_HOME",
13868
14099
  "EPOCH_PROFILE",
13869
14100
  "EPOCH_MODEL",
@@ -13878,8 +14109,6 @@ function snapshotEnv() {
13878
14109
  const snapshot = Object.fromEntries(
13879
14110
  keys.map((k) => [k, process.env[k]])
13880
14111
  );
13881
- const fromStore = getSecretValues();
13882
- for (const name of API_KEY_ENV_VARS) snapshot[`secret:${name}`] = fromStore[name];
13883
14112
  return snapshot;
13884
14113
  }
13885
14114
  function sameEnv(a, b) {
@@ -13919,6 +14148,19 @@ function loadEnvFile(path) {
13919
14148
  return {};
13920
14149
  }
13921
14150
  }
14151
+ function applyModelEntries(config, homeDir, issues) {
14152
+ if (config.modelEntries !== void 0) {
14153
+ issues.push({ path: "modelEntries", message: t("config_load.model_entries_in_yaml") });
14154
+ }
14155
+ const loaded = loadModelEntries(homeDir);
14156
+ for (const message of loaded.issues) issues.push({ path: "models.json", message });
14157
+ if (loaded.entries.length > 0) config.modelEntries = loaded.entries;
14158
+ else delete config.modelEntries;
14159
+ }
14160
+ function stampKey(path) {
14161
+ const { mtime, size } = fileStamp(path);
14162
+ return `${mtime}:${size}`;
14163
+ }
13922
14164
  function fileStamp(path) {
13923
14165
  try {
13924
14166
  const st = statSync(path);
@@ -13927,9 +14169,6 @@ function fileStamp(path) {
13927
14169
  return { mtime: 0, size: 0 };
13928
14170
  }
13929
14171
  }
13930
- function parseKeys(raw) {
13931
- return raw.split(",").map((s) => s.trim()).filter(Boolean).map((k) => ({ apiKey: k }));
13932
- }
13933
14172
  var PROBE_IMAGE_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAS0lEQVR42u3PQQkAAAgAsetfWiP4FgYrsKZeS0BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDgsqnc8OJg6Ln3AAAAAElFTkSuQmCC";
13934
14173
  var PROBE_PROMPT = "What color is this image? Answer with one word.";
13935
14174
  var MIN_IMAGE_TOKEN_DELTA = 16;
@@ -13967,14 +14206,14 @@ async function runImageProbe(model) {
13967
14206
  };
13968
14207
  }
13969
14208
  const shot = withImage.inputTokens;
13970
- const base = baseline.inputTokens;
13971
- if (shot === void 0 || base === void 0 || shot <= 0 || base <= 0) {
13972
- return { verdict: "inconclusive", detail: `(usage \u4E0D\u53EF\u7528\uFF1A\u5E26\u56FE ${shot} / \u57FA\u7EBF ${base})` };
14209
+ const base2 = baseline.inputTokens;
14210
+ if (shot === void 0 || base2 === void 0 || shot <= 0 || base2 <= 0) {
14211
+ return { verdict: "inconclusive", detail: `(usage \u4E0D\u53EF\u7528\uFF1A\u5E26\u56FE ${shot} / \u57FA\u7EBF ${base2})` };
13973
14212
  }
13974
- const delta = shot - base;
14213
+ const delta = shot - base2;
13975
14214
  return {
13976
14215
  verdict: delta >= MIN_IMAGE_TOKEN_DELTA ? "yes" : "no",
13977
- detail: `+${delta} token\uFF08\u5E26\u56FE ${shot} / \u57FA\u7EBF ${base}\uFF09\uFF1B\u5B83\u8BF4\u300C${withImage.text.slice(0, 40)}\u300D`
14216
+ detail: `+${delta} token\uFF08\u5E26\u56FE ${shot} / \u57FA\u7EBF ${base2}\uFF09\uFF1B\u5B83\u8BF4\u300C${withImage.text.slice(0, 40)}\u300D`
13978
14217
  };
13979
14218
  }
13980
14219
  var PROBE_VERSION = 2;
@@ -14013,67 +14252,6 @@ function writeImageProbe(homeDir, provider, model, supportsImages) {
14013
14252
  } catch {
14014
14253
  }
14015
14254
  }
14016
- // src/provider/fallback.ts
14017
- var DEFAULT_MODELS = {
14018
- openai: "gpt-4o-mini",
14019
- anthropic: "claude-haiku-4-5",
14020
- google: "gemini-2.0-flash",
14021
- deepseek: "deepseek-chat",
14022
- groq: "llama-3.3-70b-versatile",
14023
- mistral: "ministral-8b-latest",
14024
- moonshotai: "kimi-latest",
14025
- zhipu: "glm-4.5-flash",
14026
- dashscope: "qwen-flash",
14027
- bytedance: "doubao-seed-2.0-mini",
14028
- qianfan: "ernie-speed-128k",
14029
- cohere: "command-r7b-12-2024",
14030
- xai: "grok-vision-beta",
14031
- perplexity: "sonar",
14032
- "amazon-bedrock": "anthropic.claude-instant-v1",
14033
- azure: "gpt-4o-mini",
14034
- togetherai: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
14035
- ollama: "llama3.2",
14036
- "openai-compatible": "gpt-4o-mini"
14037
- };
14038
- function defaultModelOf(type) {
14039
- return DEFAULT_MODELS[type];
14040
- }
14041
- function modelKey(provider, model) {
14042
- return `${provider}/${model}`;
14043
- }
14044
- function nextFallbackModel(provider, current, tried, configured) {
14045
- for (const candidate of [configured, DEFAULT_MODELS[provider]]) {
14046
- if (!candidate || candidate === current) continue;
14047
- if (tried.has(modelKey(provider, candidate))) continue;
14048
- return candidate;
14049
- }
14050
- return void 0;
14051
- }
14052
- var MODEL_GIVE_UP_THRESHOLD = 3;
14053
- var ModelFailureTracker = class {
14054
- counts = /* @__PURE__ */ new Map();
14055
- abandoned = /* @__PURE__ */ new Set();
14056
- recordFailure(provider, model) {
14057
- const key = modelKey(provider, model);
14058
- if (this.abandoned.has(key)) return false;
14059
- const next = (this.counts.get(key) ?? 0) + 1;
14060
- this.counts.set(key, next);
14061
- if (next < MODEL_GIVE_UP_THRESHOLD) return false;
14062
- this.abandoned.add(key);
14063
- return true;
14064
- }
14065
- recordSuccess(provider, model) {
14066
- this.counts.delete(modelKey(provider, model));
14067
- }
14068
- isAbandoned(provider, model) {
14069
- return this.abandoned.has(modelKey(provider, model));
14070
- }
14071
- clear(provider, model) {
14072
- const key = modelKey(provider, model);
14073
- this.counts.delete(key);
14074
- this.abandoned.delete(key);
14075
- }
14076
- };
14077
14255
  var DEFAULT_CONTEXT_LENGTH2 = 2e5;
14078
14256
  var STATIC_CAPABILITIES = {
14079
14257
  "gpt-4o": {
@@ -14462,6 +14640,9 @@ function openaiCompatibleFactory(name, defaultBaseUrl) {
14462
14640
  });
14463
14641
  };
14464
14642
  }
14643
+ function base(baseUrl) {
14644
+ return baseUrl ? { baseURL: baseUrl } : {};
14645
+ }
14465
14646
  var LOADERS = {
14466
14647
  openai: async ({ apiKey, baseUrl }) => {
14467
14648
  const { createOpenAI } = await import('@ai-sdk/openai');
@@ -14469,29 +14650,29 @@ var LOADERS = {
14469
14650
  if (baseUrl) opts["baseURL"] = baseUrl;
14470
14651
  return createOpenAI(opts);
14471
14652
  },
14472
- anthropic: async ({ apiKey }) => {
14653
+ anthropic: async ({ apiKey, baseUrl }) => {
14473
14654
  const { createAnthropic } = await import('@ai-sdk/anthropic');
14474
- return createAnthropic({ apiKey });
14655
+ return createAnthropic({ apiKey, ...base(baseUrl) });
14475
14656
  },
14476
- google: async ({ apiKey }) => {
14657
+ google: async ({ apiKey, baseUrl }) => {
14477
14658
  const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
14478
- return createGoogleGenerativeAI({ apiKey });
14659
+ return createGoogleGenerativeAI({ apiKey, ...base(baseUrl) });
14479
14660
  },
14480
- deepseek: async ({ apiKey }) => {
14661
+ deepseek: async ({ apiKey, baseUrl }) => {
14481
14662
  const { createDeepSeek } = await import('@ai-sdk/deepseek');
14482
- return createDeepSeek({ apiKey });
14663
+ return createDeepSeek({ apiKey, ...base(baseUrl) });
14483
14664
  },
14484
- groq: async ({ apiKey }) => {
14665
+ groq: async ({ apiKey, baseUrl }) => {
14485
14666
  const { createGroq } = await import('@ai-sdk/groq');
14486
- return createGroq({ apiKey });
14667
+ return createGroq({ apiKey, ...base(baseUrl) });
14487
14668
  },
14488
- mistral: async ({ apiKey }) => {
14669
+ mistral: async ({ apiKey, baseUrl }) => {
14489
14670
  const { createMistral } = await import('@ai-sdk/mistral');
14490
- return createMistral({ apiKey });
14671
+ return createMistral({ apiKey, ...base(baseUrl) });
14491
14672
  },
14492
- moonshotai: async ({ apiKey }) => {
14673
+ moonshotai: async ({ apiKey, baseUrl }) => {
14493
14674
  const { createMoonshotAI } = await import('@ai-sdk/moonshotai');
14494
- return createMoonshotAI({ apiKey });
14675
+ return createMoonshotAI({ apiKey, ...base(baseUrl) });
14495
14676
  },
14496
14677
  zhipu: openaiCompatibleFactory("zhipu", "https://open.bigmodel.cn/api/paas/v4"),
14497
14678
  dashscope: async ({ apiKey, baseUrl }) => {
@@ -14504,17 +14685,17 @@ var LOADERS = {
14504
14685
  },
14505
14686
  bytedance: openaiCompatibleFactory("bytedance", "https://ark.cn-beijing.volces.com/api/v3"),
14506
14687
  qianfan: openaiCompatibleFactory("qianfan", "https://qianfan.baidubce.com/v2"),
14507
- cohere: async ({ apiKey }) => {
14688
+ cohere: async ({ apiKey, baseUrl }) => {
14508
14689
  const { createCohere } = await import('@ai-sdk/cohere');
14509
- return createCohere({ apiKey });
14690
+ return createCohere({ apiKey, ...base(baseUrl) });
14510
14691
  },
14511
- xai: async ({ apiKey }) => {
14692
+ xai: async ({ apiKey, baseUrl }) => {
14512
14693
  const { createXai } = await import('@ai-sdk/xai');
14513
- return createXai({ apiKey });
14694
+ return createXai({ apiKey, ...base(baseUrl) });
14514
14695
  },
14515
- perplexity: async ({ apiKey }) => {
14696
+ perplexity: async ({ apiKey, baseUrl }) => {
14516
14697
  const { createPerplexity } = await import('@ai-sdk/perplexity');
14517
- return createPerplexity({ apiKey });
14698
+ return createPerplexity({ apiKey, ...base(baseUrl) });
14518
14699
  },
14519
14700
  "amazon-bedrock": async ({ apiKey }) => {
14520
14701
  const { createAmazonBedrock } = await import('@ai-sdk/amazon-bedrock');
@@ -14524,13 +14705,13 @@ var LOADERS = {
14524
14705
  const { createAzure } = await import('@ai-sdk/azure');
14525
14706
  return createAzure({ apiKey });
14526
14707
  },
14527
- togetherai: async ({ apiKey }) => {
14708
+ togetherai: async ({ apiKey, baseUrl }) => {
14528
14709
  const { createTogetherAI } = await import('@ai-sdk/togetherai');
14529
- return createTogetherAI({ apiKey });
14710
+ return createTogetherAI({ apiKey, ...base(baseUrl) });
14530
14711
  },
14531
- ollama: async () => {
14712
+ ollama: async ({ baseUrl }) => {
14532
14713
  const { createOllama } = await import('ollama-ai-provider-v2');
14533
- return createOllama();
14714
+ return createOllama(base(baseUrl));
14534
14715
  },
14535
14716
  "openai-compatible": openaiCompatibleFactory("custom", "http://localhost:8080/v1")
14536
14717
  };
@@ -14558,6 +14739,62 @@ async function createModelFactory(type, opts) {
14558
14739
  throw err;
14559
14740
  }
14560
14741
  }
14742
+ async function createEntryFactory(entry, apiKey) {
14743
+ const type = entry.provider;
14744
+ const pkg = type === "openai-compatible" && entry.protocol === "anthropic-messages" ? "@ai-sdk/anthropic" : PACKAGES[type];
14745
+ try {
14746
+ return await loadEntry(entry, apiKey);
14747
+ } catch (err) {
14748
+ if (isModuleNotFound(err)) throw new ProviderSdkMissingError(type, pkg, err);
14749
+ throw err;
14750
+ }
14751
+ }
14752
+ async function loadEntry(entry, apiKey) {
14753
+ const baseURL = entry.baseUrl;
14754
+ if (entry.provider === "openai-compatible") {
14755
+ if (entry.protocol === "anthropic-messages") {
14756
+ const { createAnthropic } = await import('@ai-sdk/anthropic');
14757
+ return createAnthropic({ apiKey: apiKey ?? "", ...baseURL ? { baseURL } : {} });
14758
+ }
14759
+ const { createOpenAICompatible } = await import('@ai-sdk/openai-compatible');
14760
+ return createOpenAICompatible({
14761
+ ...apiKey ? { apiKey } : {},
14762
+ baseURL: baseURL || "http://localhost:8080/v1",
14763
+ name: "custom",
14764
+ includeUsage: true,
14765
+ ...entry.rawUrl && baseURL ? { fetch: rawUrlFetch(baseURL) } : {}
14766
+ });
14767
+ }
14768
+ const key = apiKey ?? "";
14769
+ return LOADERS[entry.provider](baseURL ? { apiKey: key, baseUrl: baseURL } : { apiKey: key });
14770
+ }
14771
+ function rawUrlFetch(url) {
14772
+ return (input, init) => globalThis.fetch(input instanceof Request ? new Request(url, input) : url, init);
14773
+ }
14774
+ var TOOL_RESULT_MEDIA = {
14775
+ anthropic: true,
14776
+ openai: true,
14777
+ google: true,
14778
+ azure: true,
14779
+ "amazon-bedrock": true,
14780
+ deepseek: false,
14781
+ "openai-compatible": false,
14782
+ zhipu: false,
14783
+ bytedance: false,
14784
+ qianfan: false,
14785
+ groq: false,
14786
+ mistral: false,
14787
+ dashscope: false,
14788
+ xai: false,
14789
+ cohere: false,
14790
+ moonshotai: false,
14791
+ togetherai: false,
14792
+ perplexity: false,
14793
+ ollama: false
14794
+ };
14795
+ function acceptsToolResultMedia(type) {
14796
+ return TOOL_RESULT_MEDIA[type];
14797
+ }
14561
14798
  function isModuleNotFound(err) {
14562
14799
  if (!err || typeof err !== "object") return false;
14563
14800
  const code = err.code;
@@ -14565,21 +14802,127 @@ function isModuleNotFound(err) {
14565
14802
  const msg = err instanceof Error ? err.message : "";
14566
14803
  return /Cannot find (module|package)|Failed to resolve/i.test(msg);
14567
14804
  }
14805
+ // src/provider/entry-route.ts
14806
+ function buildEntryTable(entries2) {
14807
+ return {
14808
+ entries: new Map((entries2 ?? []).map((e) => [e.id, e]))
14809
+ };
14810
+ }
14811
+ function entryProvider(_table, entry) {
14812
+ return entry.provider;
14813
+ }
14814
+ function entrySelection(table, entry, origin) {
14815
+ return { provider: entryProvider(table, entry), model: entry.model, origin, entry: entry.id };
14816
+ }
14817
+ function routeKey(table, provider, entry) {
14818
+ if (!table.entries.has(entry)) return provider;
14819
+ return `${ENTRY_REF_PREFIX}${entry}`;
14820
+ }
14821
+ function applyEntryCapability(base2, entry) {
14822
+ if (!entry) return base2;
14823
+ const caps = entry.capabilities ?? {};
14824
+ const out = { ...base2 };
14825
+ if (caps.tools !== void 0) out.supportsTools = caps.tools;
14826
+ if (caps.images !== void 0) {
14827
+ out.supportsImages = caps.images;
14828
+ delete out.imagesUnknown;
14829
+ }
14830
+ if (caps.reasoning !== void 0) out.supportsReasoning = caps.reasoning;
14831
+ if (entry.contextLength) {
14832
+ out.contextLength = entry.contextLength;
14833
+ delete out.contextLengthUnknown;
14834
+ }
14835
+ if (entry.maxOutputTokens) out.maxOutputTokens = entry.maxOutputTokens;
14836
+ return out;
14837
+ }
14838
+ function entryAcceptsToolMedia(table, provider, entry) {
14839
+ const e = table.entries.get(entry);
14840
+ if (e?.provider === "openai-compatible" && e.protocol === "anthropic-messages") return true;
14841
+ return acceptsToolResultMedia(provider);
14842
+ }
14843
+ function capabilityOfRef(config, ref = config.model) {
14844
+ const parsed = ref ? parseModelRef(ref) : void 0;
14845
+ const entry = parsed ? config.modelEntries?.find((e) => e.id === parsed.entry) : void 0;
14846
+ if (!entry) return void 0;
14847
+ return applyEntryCapability(getCapability(entry.model, config.homeDir, entry.provider), entry);
14848
+ }
14849
+ // src/provider/fallback.ts
14850
+ function modelKey(provider, model) {
14851
+ return `${provider}/${model}`;
14852
+ }
14853
+ var MODEL_GIVE_UP_THRESHOLD = 3;
14854
+ var ModelFailureTracker = class {
14855
+ counts = /* @__PURE__ */ new Map();
14856
+ abandoned = /* @__PURE__ */ new Set();
14857
+ recordFailure(key) {
14858
+ if (this.abandoned.has(key)) return false;
14859
+ const next = (this.counts.get(key) ?? 0) + 1;
14860
+ this.counts.set(key, next);
14861
+ if (next < MODEL_GIVE_UP_THRESHOLD) return false;
14862
+ this.abandoned.add(key);
14863
+ return true;
14864
+ }
14865
+ recordSuccess(key) {
14866
+ this.counts.delete(key);
14867
+ }
14868
+ isAbandoned(key) {
14869
+ return this.abandoned.has(key);
14870
+ }
14871
+ clear(key) {
14872
+ this.counts.delete(key);
14873
+ this.abandoned.delete(key);
14874
+ }
14875
+ };
14876
+ // src/provider/reasoning-options.ts
14877
+ function maxOptions(dialect) {
14878
+ switch (dialect) {
14879
+ case "anthropic":
14880
+ return { anthropic: { effort: "max" } };
14881
+ case "deepseek":
14882
+ return { deepseek: { reasoningEffort: "max" } };
14883
+ case "openai":
14884
+ return { openai: { reasoningEffort: "max" } };
14885
+ case "openai-compatible":
14886
+ return { openaiCompatible: { reasoningEffort: "max" } };
14887
+ default:
14888
+ return void 0;
14889
+ }
14890
+ }
14891
+ function dialectOf(provider, entry) {
14892
+ if (provider === "openai-compatible") {
14893
+ return entry?.protocol === "anthropic-messages" ? "anthropic" : "openai-compatible";
14894
+ }
14895
+ if (provider === "zhipu" || provider === "bytedance" || provider === "qianfan") {
14896
+ return "openai-compatible";
14897
+ }
14898
+ if (provider === "anthropic" || provider === "deepseek" || provider === "openai") return provider;
14899
+ return "other";
14900
+ }
14901
+ function reasoningArgs(choice, dialect) {
14902
+ if (choice === void 0) return void 0;
14903
+ if (choice === "off") return { reasoning: "none" };
14904
+ if (choice !== "max") return { reasoning: choice };
14905
+ const extra = maxOptions(dialect);
14906
+ return extra ? { reasoning: "xhigh", providerOptions: extra } : { reasoning: "xhigh" };
14907
+ }
14908
+ function mergeProviderOptions(base2, extra) {
14909
+ if (!extra) return base2;
14910
+ const out = { ...base2 };
14911
+ for (const [key, value] of Object.entries(extra)) out[key] = { ...out[key], ...value };
14912
+ return out;
14913
+ }
14568
14914
  var CONTEXT_WARN_RATIO = 0.8;
14569
14915
  function fmtTokens(n) {
14570
14916
  return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
14571
14917
  }
14572
14918
  function checkCredential(want, deps) {
14573
14919
  if (deps.hasCredential(want.provider)) return null;
14574
- return { code: "no-credential", provider: want.provider, envVar: apiKeyEnvVar(want.provider) };
14920
+ return { code: "no-credential", entry: want.entry };
14575
14921
  }
14576
14922
  function describeRejection(rejection) {
14577
14923
  switch (rejection.code) {
14578
14924
  case "no-credential":
14579
- return t("provider_switch.no_credential", {
14580
- provider: rejection.provider,
14581
- hint: rejection.envVar ? t("provider_switch.no_credential_env", { envVar: rejection.envVar }) : t("provider_switch.no_credential_plain")
14582
- });
14925
+ return t("provider_switch.no_credential", { entry: rejection.entry });
14583
14926
  case "images-unsupported":
14584
14927
  return t("provider_switch.images_unsupported", { model: rejection.model });
14585
14928
  case "context-overflow":
@@ -14588,6 +14931,8 @@ function describeRejection(rejection) {
14588
14931
  model: rejection.model,
14589
14932
  limit: fmtTokens(rejection.contextLength)
14590
14933
  });
14934
+ case "unknown-entry":
14935
+ return t("provider_switch.unknown_entry", { entry: rejection.entry });
14591
14936
  }
14592
14937
  }
14593
14938
  function describeWarning(warning) {
@@ -14639,15 +14984,13 @@ function evaluateSelection(want, deps, ctx = {}) {
14639
14984
  function reject(rejection) {
14640
14985
  return { ok: false, reason: describeRejection(rejection), rejection };
14641
14986
  }
14642
- // src/provider/types.ts
14643
- var DEFAULT_MODEL = "gpt-4o-mini";
14644
14987
  // src/provider/router.ts
14645
14988
  var ProviderRouter = class {
14646
14989
  config;
14647
14990
  available = /* @__PURE__ */ new Map();
14648
14991
  factories = /* @__PURE__ */ new Map();
14649
- credentialIndex = 0;
14650
- keyEnv;
14992
+ entryTable;
14993
+ effort;
14651
14994
  telemetry = NOOP_TELEMETRY;
14652
14995
  selection;
14653
14996
  configSelection;
@@ -14658,44 +15001,28 @@ var ProviderRouter = class {
14658
15001
  }
14659
15002
  constructor(config) {
14660
15003
  this.config = config;
14661
- this.keyEnv = readProviderKeyEnv();
14662
- this.initFactories(config);
14663
- this.configSelection = {
14664
- provider: config.provider.type,
14665
- model: config.model ?? DEFAULT_MODEL,
14666
- origin: "config"
14667
- };
15004
+ this.entryTable = buildEntryTable(config.modelEntries);
15005
+ this.configSelection = this.resolveConfigured(config);
15006
+ this.initFactories();
14668
15007
  this.selection = { ...this.configSelection };
14669
15008
  }
14670
15009
  getSelection() {
14671
15010
  return { ...this.selection };
14672
15011
  }
14673
15012
  getConfiguredModel() {
14674
- return this.configSelection.model;
15013
+ return entryRef(this.configSelection.entry);
14675
15014
  }
14676
15015
  setSelection(next, ctx = {}) {
14677
- return this.applySelection(
14678
- {
14679
- provider: next.provider ?? this.selection.provider,
14680
- model: next.model,
14681
- origin: "user-switch"
14682
- },
14683
- ctx
14684
- );
15016
+ const want = this.resolveRef(next, "user-switch");
15017
+ return "ok" in want ? want : this.applySelection(want, ctx);
14685
15018
  }
14686
15019
  resetSelection(ctx = {}) {
14687
15020
  return this.applySelection({ ...this.configSelection }, ctx);
14688
15021
  }
14689
15022
  enterTurn(ref, ctx = {}) {
14690
15023
  const previous = { ...this.selection };
14691
- const result = this.applySelection(
14692
- {
14693
- provider: ref.provider ?? this.selection.provider,
14694
- model: ref.model,
14695
- origin: "command-frontmatter"
14696
- },
14697
- ctx
14698
- );
15024
+ const want = this.resolveRef(ref, "command-frontmatter");
15025
+ const result = "ok" in want ? want : this.applySelection(want, ctx);
14699
15026
  return {
14700
15027
  result,
14701
15028
  restore: () => {
@@ -14707,15 +15034,16 @@ var ProviderRouter = class {
14707
15034
  const result = evaluateSelection(
14708
15035
  want,
14709
15036
  {
14710
- hasCredential: (p) => this.available.has(p),
14711
- capability: (model) => getCapability(model, this.config.homeDir, want.provider)
15037
+ hasCredential: (p) => this.available.has(this.routeOf(p, want.entry)),
15038
+ capability: (model) => this.capabilityFor(want.provider, model, want.entry)
14712
15039
  },
14713
15040
  ctx
14714
15041
  );
14715
15042
  if (!result.ok) return result;
14716
15043
  const from = this.selection;
14717
15044
  this.selection = want;
14718
- this.failures.clear(want.provider, want.model);
15045
+ this.effort = void 0;
15046
+ this.failures.clear(this.routeOf(want.provider, want.entry));
14719
15047
  this.telemetry.counter(METRIC.MODEL_SWITCH, 1, {
14720
15048
  "epoch.model.from": modelKey(from.provider, from.model),
14721
15049
  "epoch.model.to": modelKey(want.provider, want.model),
@@ -14723,9 +15051,77 @@ var ProviderRouter = class {
14723
15051
  });
14724
15052
  return result;
14725
15053
  }
15054
+ reloadModelEntries(entries2) {
15055
+ this.entryTable = buildEntryTable(entries2);
15056
+ for (const [route, opts] of [...this.available]) {
15057
+ if (!opts.entry) continue;
15058
+ this.available.delete(route);
15059
+ this.factories.delete(route);
15060
+ }
15061
+ this.registerEntries();
15062
+ const configured = this.refreshedSelection(this.configSelection);
15063
+ if (configured) this.configSelection = configured;
15064
+ else if (this.entryTable.entries.has(this.configSelection.entry))
15065
+ this.notices.push(
15066
+ t("provider_route.entry_no_credential", { entry: this.configSelection.entry })
15067
+ );
15068
+ else
15069
+ this.notices.push(t("provider_route.entry_missing", { entry: this.configSelection.entry }));
15070
+ const selection = this.refreshedSelection(this.selection);
15071
+ if (selection) {
15072
+ this.selection = selection;
15073
+ } else if (configured) {
15074
+ const gone = this.selection.entry;
15075
+ this.selection = { ...configured };
15076
+ this.effort = void 0;
15077
+ this.notices.push(
15078
+ this.entryTable.entries.has(gone) ? t("provider_route.entry_unusable", { entry: gone, model: entryRef(configured.entry) }) : t("provider_route.entry_removed", { entry: gone, model: entryRef(configured.entry) })
15079
+ );
15080
+ }
15081
+ if (this.effort !== void 0) {
15082
+ const state = this.getEffort();
15083
+ const ok2 = this.effort === "off" ? state.canTurnOff : state.levels.includes(this.effort);
15084
+ if (!ok2) this.effort = void 0;
15085
+ }
15086
+ }
15087
+ refreshedSelection(sel) {
15088
+ const entry = this.entryTable.entries.get(sel.entry);
15089
+ if (!entry) return void 0;
15090
+ const next = entrySelection(this.entryTable, entry, sel.origin);
15091
+ return this.available.has(this.routeOf(next.provider, next.entry)) ? next : void 0;
15092
+ }
14726
15093
  drainNotices() {
14727
15094
  return this.notices.splice(0, this.notices.length);
14728
15095
  }
15096
+ getEffort() {
15097
+ const entry = this.entryTable.entries.get(this.selection.entry);
15098
+ const caps = entry?.capabilities;
15099
+ if (!entry || !caps?.reasoning)
15100
+ return { levels: [], canTurnOff: false, choice: null, default: null };
15101
+ return {
15102
+ levels: entry.reasoning?.levels ?? [],
15103
+ canTurnOff: caps.reasoningToggle === true && caps.reasoningOnly !== true,
15104
+ choice: this.effort ?? null,
15105
+ default: entry.reasoning?.default ?? null
15106
+ };
15107
+ }
15108
+ setEffort(choice) {
15109
+ const state = this.getEffort();
15110
+ if (choice === null) {
15111
+ this.effort = void 0;
15112
+ return true;
15113
+ }
15114
+ const ok2 = choice === "off" ? state.canTurnOff : state.levels.includes(choice);
15115
+ if (ok2) this.effort = choice;
15116
+ return ok2;
15117
+ }
15118
+ reasoningFor(input, ct, entry) {
15119
+ if (input.entry || entry !== this.selection.entry) return void 0;
15120
+ const e = this.entryTable.entries.get(entry);
15121
+ if (!e?.capabilities?.reasoning) return void 0;
15122
+ const choice = this.effort ?? e.reasoning?.default;
15123
+ return reasoningArgs(choice, dialectOf(ct, e));
15124
+ }
14729
15125
  getUtilityInfo() {
14730
15126
  const raw = this.config.models?.utility;
14731
15127
  if (!raw) return { selection: this.getSelection(), configured: false };
@@ -14737,149 +15133,129 @@ var ProviderRouter = class {
14737
15133
  issue: t("provider_route.utility_unparsable", { raw })
14738
15134
  };
14739
15135
  }
14740
- const provider = ref.provider ?? this.selection.provider;
14741
- if (!this.available.has(provider)) {
14742
- return {
14743
- selection: this.getSelection(),
14744
- configured: true,
14745
- issue: t("provider_route.utility_no_credential", { provider })
14746
- };
14747
- }
14748
- return {
14749
- selection: { provider, model: ref.model, origin: this.selection.origin },
14750
- configured: true
14751
- };
15136
+ return this.entryUtilityInfo(ref.entry);
14752
15137
  }
14753
- initFactories(config) {
14754
- const haveCred = config.provider.credentials[0]?.apiKey || config.provider.apiKey;
14755
- if (!haveCred) {
14756
- throw new ProviderError(t("provider_route.no_credential"), PROVIDER_NO_CREDENTIAL, false);
14757
- }
14758
- for (const type of PROVIDER_TYPES) {
14759
- const key = this.resolveKey(type, config);
14760
- if (!key) continue;
14761
- const isPrimary = type === config.provider.type;
14762
- this.register(type, key, isPrimary ? config.provider.baseUrl : void 0);
14763
- }
14764
- if (!this.available.has(config.provider.type)) {
15138
+ initFactories() {
15139
+ this.registerEntries();
15140
+ const { provider, entry } = this.configSelection;
15141
+ if (!this.available.has(this.routeOf(provider, entry))) {
14765
15142
  throw new ProviderError(
14766
- t("provider_route.provider_no_credential", { provider: config.provider.type }),
15143
+ t("provider_route.entry_no_credential", { entry }),
14767
15144
  PROVIDER_NO_CREDENTIAL,
14768
15145
  false
14769
15146
  );
14770
15147
  }
14771
- if (config.fallback) {
14772
- for (const fbType of config.fallback.providers) {
14773
- if (this.available.has(fbType)) continue;
14774
- const fbKey = this.getFallbackKey(fbType);
14775
- if (fbKey) this.register(fbType, fbKey);
14776
- }
15148
+ }
15149
+ registerEntries() {
15150
+ for (const entry of this.entryTable.entries.values()) {
15151
+ const key = readEntryKey(entry.id, this.config.homeDir);
15152
+ if (!key && !entry.host && entry.provider !== "openai-compatible" && entry.provider !== "ollama")
15153
+ continue;
15154
+ this.available.set(this.routeOf(entry.provider, entry.id), { apiKey: key ?? "", entry });
14777
15155
  }
14778
15156
  }
14779
- register(type, apiKey, baseUrl) {
14780
- this.available.set(type, baseUrl ? { apiKey, baseUrl } : { apiKey });
14781
- this.factories.delete(type);
15157
+ routeOf(provider, entry) {
15158
+ return routeKey(this.entryTable, provider, entry);
15159
+ }
15160
+ capabilityFor(provider, model, entry) {
15161
+ return applyEntryCapability(
15162
+ getCapability(model, this.config.homeDir, provider),
15163
+ this.entryTable.entries.get(entry)
15164
+ );
15165
+ }
15166
+ resolveConfigured(config) {
15167
+ const ref = config.model ? parseModelRef(config.model) : void 0;
15168
+ const entry = ref ? this.entryTable.entries.get(ref.entry) : void 0;
15169
+ if (entry) return entrySelection(this.entryTable, entry, "config");
15170
+ throw new ProviderError(
15171
+ ref ? t("provider_route.entry_missing", { entry: ref.entry }) : t("provider_route.no_model"),
15172
+ PROVIDER_NO_CREDENTIAL,
15173
+ false
15174
+ );
14782
15175
  }
14783
- resolveKey(type, config) {
14784
- if (type === config.provider.type) {
14785
- return config.provider.apiKey || config.provider.credentials[0]?.apiKey;
15176
+ resolveRef(ref, origin) {
15177
+ const entry = this.entryTable.entries.get(ref.entry);
15178
+ if (!entry) {
15179
+ const rejection = { code: "unknown-entry", entry: ref.entry };
15180
+ return { ok: false, reason: describeRejection(rejection), rejection };
14786
15181
  }
14787
- return this.getFallbackKey(type);
15182
+ return entrySelection(this.entryTable, entry, origin);
14788
15183
  }
14789
- getFallbackKey(type) {
14790
- if (type === "ollama") return "local";
14791
- const envName = apiKeyEnvVar(type);
14792
- if (!envName) return void 0;
14793
- return this.keyEnv[envName]?.split(",")[0]?.trim() || void 0;
15184
+ entryUtilityInfo(id) {
15185
+ const entry = this.entryTable.entries.get(id);
15186
+ if (!entry) {
15187
+ return {
15188
+ selection: this.getSelection(),
15189
+ configured: true,
15190
+ issue: t("provider_route.utility_unknown_entry", { entry: id })
15191
+ };
15192
+ }
15193
+ const selection = entrySelection(this.entryTable, entry, this.selection.origin);
15194
+ if (!this.available.has(this.routeOf(selection.provider, selection.entry))) {
15195
+ return {
15196
+ selection: this.getSelection(),
15197
+ configured: true,
15198
+ issue: t("provider_route.entry_no_credential", { entry: id })
15199
+ };
15200
+ }
15201
+ return { selection, configured: true };
14794
15202
  }
14795
- async getModel(type, modelId) {
14796
- const cached = this.factories.get(type);
15203
+ async getModel(route, modelId) {
15204
+ const cached = this.factories.get(route);
14797
15205
  if (cached) return cached(modelId ?? this.selection.model);
14798
- const opts = this.available.get(type);
15206
+ const opts = this.available.get(route);
14799
15207
  if (!opts) {
14800
- throw new ProviderError(t("provider_route.not_initialized", { provider: type }), 1004, false);
15208
+ throw new ProviderError(
15209
+ t("provider_route.not_initialized", { provider: route }),
15210
+ 1004,
15211
+ false
15212
+ );
14801
15213
  }
14802
15214
  let factory;
14803
15215
  try {
14804
- factory = await createModelFactory(type, opts);
15216
+ factory = opts.entry ? await createEntryFactory(opts.entry, opts.apiKey || void 0) : await createModelFactory(route, opts);
14805
15217
  } catch (err) {
14806
15218
  throw new ProviderError(err instanceof Error ? err.message : String(err), 1005, false);
14807
15219
  }
14808
- this.factories.set(type, factory);
15220
+ this.factories.set(route, factory);
14809
15221
  return factory(modelId ?? this.selection.model);
14810
15222
  }
14811
- rotateCredential() {
14812
- const creds = this.config.provider.credentials;
14813
- if (this.credentialIndex + 1 < creds.length) {
14814
- this.credentialIndex++;
14815
- const newCred = creds[this.credentialIndex];
14816
- if (!newCred) return false;
14817
- this.register(this.config.provider.type, newCred.apiKey, this.config.provider.baseUrl);
14818
- return true;
14819
- }
14820
- return false;
14821
- }
14822
- fallbackProvider(currentType, tried) {
14823
- if (!this.config.fallback) return null;
14824
- for (const fbType of this.config.fallback.providers) {
14825
- if (fbType === currentType) continue;
14826
- if (tried?.has(modelKey(fbType, defaultModelOf(fbType)))) continue;
14827
- if (this.available.has(fbType)) {
14828
- this.telemetry.counter(METRIC.PROVIDER_FALLBACK, 1, {
14829
- "epoch.provider.from": currentType,
14830
- "epoch.provider.to": fbType
14831
- });
14832
- return {
14833
- from: currentType,
14834
- fromModel: this.getModelId(),
14835
- to: fbType,
14836
- toModel: defaultModelOf(fbType),
14837
- reason: t("provider_route.fallback_reason")
14838
- };
14839
- }
14840
- }
14841
- return null;
14842
- }
14843
15223
  startPoint(input) {
14844
15224
  if (input.responseFormat && input.tools?.length) {
14845
15225
  throw new ProviderError(t("provider.response_format_with_tools"), 1002, false);
14846
15226
  }
14847
- const ct = input.provider ?? this.selection.provider;
14848
- if (input.provider && !this.available.has(input.provider)) {
14849
- throw new ProviderError(
14850
- t("provider_route.cannot_use", { provider: input.provider }),
14851
- 1001,
14852
- false
14853
- );
15227
+ const id = input.entry ?? this.selection.entry;
15228
+ const entry = this.entryTable.entries.get(id);
15229
+ const sel = entry ? entrySelection(this.entryTable, entry, "config") : void 0;
15230
+ if (!sel || !this.available.has(this.routeOf(sel.provider, sel.entry))) {
15231
+ throw new ProviderError(t("provider_route.cannot_use", { entry: id }), 1001, false);
14854
15232
  }
14855
- const wanted = input.model ?? (input.provider && input.provider !== this.selection.provider ? defaultModelOf(input.provider) : this.selection.model);
14856
- const cm = input.model ? wanted : this.avoidAbandoned(ct, wanted);
14857
- return { ct, cm, attempt: 0, tried: /* @__PURE__ */ new Set([modelKey(ct, cm)]) };
15233
+ const start = input.entry ? sel : this.avoidAbandoned(sel);
15234
+ return {
15235
+ ct: start.provider,
15236
+ cm: start.model,
15237
+ entry: start.entry,
15238
+ attempt: 0,
15239
+ tried: /* @__PURE__ */ new Set([entryTried(start.entry)])
15240
+ };
14858
15241
  }
14859
- avoidAbandoned(ct, cm) {
14860
- if (!this.failures.isAbandoned(ct, cm)) return cm;
14861
- const seen = /* @__PURE__ */ new Set([modelKey(ct, cm)]);
14862
- for (; ; ) {
14863
- const next = nextFallbackModel(ct, cm, seen, this.configuredFallbackModel(ct));
14864
- if (!next) return cm;
14865
- if (!this.failures.isAbandoned(ct, next)) return next;
14866
- seen.add(modelKey(ct, next));
14867
- }
15242
+ avoidAbandoned(sel) {
15243
+ if (!this.failures.isAbandoned(this.routeOf(sel.provider, sel.entry))) return sel;
15244
+ const fb = this.fallbackEntry(sel.entry, /* @__PURE__ */ new Set([entryTried(sel.entry)]));
15245
+ return fb && !this.failures.isAbandoned(this.routeOf(fb.provider, fb.entry)) ? fb : sel;
14868
15246
  }
14869
15247
  async recover(err, state) {
14870
15248
  const { code, recovery } = classifyProviderError(err);
14871
15249
  const message = describeProviderError(err);
14872
- const stay = { kind: "continue", ct: state.ct, cm: state.cm };
15250
+ const stay = { kind: "continue", ct: state.ct, cm: state.cm, entry: state.entry };
14873
15251
  if (code === PROVIDER_ABORTED) return { kind: "give-up", message, code };
14874
- if (recovery === "rotate_credential" && this.rotateCredential())
14875
- return { ...stay, attempt: state.attempt + 1 };
14876
15252
  if (recovery === "retry") {
14877
15253
  await this.backoff(state.attempt);
14878
15254
  return { ...stay, attempt: state.attempt + 1 };
14879
15255
  }
14880
- this.noteFailure(state.ct, state.cm);
15256
+ this.noteFailure(state);
14881
15257
  if (recovery === "abort") return { kind: "give-up", message, code };
14882
- const next = this.escalate(state, { sameProviderFirst: recovery === "switch_model" });
15258
+ const next = this.escalate(state);
14883
15259
  if (next) return next;
14884
15260
  return {
14885
15261
  kind: "give-up",
@@ -14890,36 +15266,40 @@ var ProviderRouter = class {
14890
15266
  code
14891
15267
  };
14892
15268
  }
14893
- escalate(state, opts) {
14894
- if (opts.sameProviderFirst) {
14895
- const next = nextFallbackModel(
14896
- state.ct,
14897
- state.cm,
14898
- state.tried,
14899
- this.configuredFallbackModel(state.ct)
14900
- );
14901
- if (next) {
14902
- this.telemetry.counter(METRIC.MODEL_FALLBACK, 1, {
14903
- "epoch.model.from": modelKey(state.ct, state.cm),
14904
- "epoch.model.to": modelKey(state.ct, next)
14905
- });
14906
- state.tried.add(modelKey(state.ct, next));
14907
- return { kind: "continue", ct: state.ct, cm: next, attempt: 0 };
14908
- }
14909
- }
14910
- const fb = this.fallbackProvider(state.ct, state.tried);
14911
- if (!fb) return null;
14912
- state.tried.add(modelKey(fb.to, fb.toModel));
14913
- return { kind: "continue", ct: fb.to, cm: fb.toModel, attempt: 0 };
15269
+ escalate(state) {
15270
+ const target = this.fallbackEntry(state.entry, state.tried);
15271
+ if (!target) return null;
15272
+ this.telemetry.counter(METRIC.MODEL_FALLBACK, 1, {
15273
+ "epoch.model.from": modelKey(state.ct, state.cm),
15274
+ "epoch.model.to": modelKey(target.provider, target.model)
15275
+ });
15276
+ state.tried.add(entryTried(target.entry));
15277
+ return {
15278
+ kind: "continue",
15279
+ ct: target.provider,
15280
+ cm: target.model,
15281
+ entry: target.entry,
15282
+ attempt: 0
15283
+ };
15284
+ }
15285
+ afterRetries(state) {
15286
+ this.noteFailure(state);
15287
+ return this.escalate(state);
14914
15288
  }
14915
- configuredFallbackModel(ct) {
14916
- return ct === this.config.provider.type ? this.config.fallback?.model : void 0;
15289
+ fallbackEntry(current, tried) {
15290
+ const raw = this.config.fallback?.model;
15291
+ const id = raw ? parseModelRef(raw)?.entry : void 0;
15292
+ const entry = id ? this.entryTable.entries.get(id) : void 0;
15293
+ if (!entry || entry.id === current || tried.has(entryTried(entry.id))) return void 0;
15294
+ const sel = entrySelection(this.entryTable, entry, "fallback");
15295
+ return this.available.has(this.routeOf(sel.provider, sel.entry)) ? sel : void 0;
14917
15296
  }
14918
- noteFailure(provider, model) {
14919
- if (!this.failures.recordFailure(provider, model)) return;
15297
+ noteFailure(state) {
15298
+ if (!this.failures.recordFailure(this.routeOf(state.ct, state.entry))) return;
14920
15299
  this.notices.push(
14921
15300
  t("provider_route.give_up_model", {
14922
- model: modelKey(provider, model),
15301
+ entry: state.entry,
15302
+ model: state.cm,
14923
15303
  count: MODEL_GIVE_UP_THRESHOLD
14924
15304
  })
14925
15305
  );
@@ -14931,30 +15311,50 @@ var ProviderRouter = class {
14931
15311
  async generate(input) {
14932
15312
  const state = this.startPoint(input);
14933
15313
  const requestedModel = state.cm;
14934
- const messages = toModelMessages(input.messages);
15314
+ const messagesFor = messageTranslator(input.messages, this.entryTable);
14935
15315
  const tools = input.tools ? toSdkTools(input.tools) : void 0;
14936
15316
  const output = input.responseFormat ? toOutputSpec(input.responseFormat) : void 0;
14937
- while (state.attempt <= this.config.retry.maxRetries) {
14938
- const { ct, cm } = state;
15317
+ for (; ; ) {
15318
+ if (state.attempt > this.config.retry.maxRetries) {
15319
+ const next = this.afterRetries(state);
15320
+ if (!next) {
15321
+ throw new ProviderError(
15322
+ t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries }),
15323
+ 9999,
15324
+ false
15325
+ );
15326
+ }
15327
+ state.ct = next.ct;
15328
+ state.cm = next.cm;
15329
+ state.entry = next.entry;
15330
+ state.attempt = next.attempt;
15331
+ continue;
15332
+ }
15333
+ const { ct, cm, entry } = state;
14939
15334
  try {
14940
- const model = await this.getModel(ct, cm);
15335
+ const model = await this.getModel(this.routeOf(ct, entry), cm);
15336
+ const maxTokens = this.maxTokensFor(input, entry);
15337
+ const effort = this.reasoningFor(input, ct, entry);
14941
15338
  const result = await generateText({
14942
15339
  model,
14943
- messages,
15340
+ messages: messagesFor(ct, entry),
14944
15341
  tools,
14945
15342
  system: input.system,
14946
15343
  stopWhen: isStepCount(input.maxSteps ?? 1),
14947
- ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
15344
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {},
14948
15345
  ...input.signal ? { abortSignal: input.signal } : {},
14949
15346
  output,
14950
- providerOptions: toSdkProviderOptions(input.providerOptions)
15347
+ providerOptions: toSdkProviderOptions(
15348
+ mergeProviderOptions(input.providerOptions, effort?.providerOptions)
15349
+ ),
15350
+ ...effort ? { reasoning: effort.reasoning } : {}
14951
15351
  });
14952
15352
  const toolCalls = normalizeToolCalls(
14953
15353
  result.toolCalls
14954
15354
  );
14955
15355
  const steps = result.steps;
14956
15356
  const reasoningText = steps?.map((s) => s.reasoningText).filter(Boolean).join("\n") || void 0;
14957
- this.failures.recordSuccess(ct, cm);
15357
+ this.failures.recordSuccess(this.routeOf(ct, entry));
14958
15358
  const usage = toTokenUsage(result.usage);
14959
15359
  const warnings = toProviderWarnings(result.warnings);
14960
15360
  const applied = input.responseFormat ? didApplyResponseFormat(result.warnings) : void 0;
@@ -14976,39 +15376,54 @@ var ProviderRouter = class {
14976
15376
  if (plan.kind === "give-up") throw new ProviderError(plan.message, plan.code, false);
14977
15377
  state.ct = plan.ct;
14978
15378
  state.cm = plan.cm;
15379
+ state.entry = plan.entry;
14979
15380
  state.attempt = plan.attempt;
14980
15381
  }
14981
15382
  }
14982
- this.noteFailure(state.ct, state.cm);
14983
- throw new ProviderError(
14984
- t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries }),
14985
- 9999,
14986
- false
14987
- );
14988
15383
  }
14989
15384
  async *stream(input) {
14990
15385
  const state = this.startPoint(input);
14991
15386
  const requestedModel = state.cm;
14992
15387
  let emitted = false;
14993
- const messages = toModelMessages(input.messages);
15388
+ const messagesFor = messageTranslator(input.messages, this.entryTable);
14994
15389
  const tools = input.tools ? toSdkTools(input.tools) : void 0;
14995
15390
  const output = input.responseFormat ? toOutputSpec(input.responseFormat) : void 0;
14996
15391
  let streamPartError;
14997
- while (state.attempt <= this.config.retry.maxRetries) {
14998
- const { ct, cm } = state;
15392
+ for (; ; ) {
15393
+ if (state.attempt > this.config.retry.maxRetries) {
15394
+ const next = this.afterRetries(state);
15395
+ if (!next) {
15396
+ yield {
15397
+ type: "error",
15398
+ error: t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries })
15399
+ };
15400
+ return;
15401
+ }
15402
+ state.ct = next.ct;
15403
+ state.cm = next.cm;
15404
+ state.entry = next.entry;
15405
+ state.attempt = next.attempt;
15406
+ continue;
15407
+ }
15408
+ const { ct, cm, entry } = state;
14999
15409
  streamPartError = void 0;
15000
15410
  try {
15001
- const model = await this.getModel(ct, cm);
15411
+ const model = await this.getModel(this.routeOf(ct, entry), cm);
15412
+ const maxTokens = this.maxTokensFor(input, entry);
15413
+ const effort = this.reasoningFor(input, ct, entry);
15002
15414
  const result = streamText({
15003
15415
  model,
15004
- messages,
15416
+ messages: messagesFor(ct, entry),
15005
15417
  tools,
15006
15418
  system: input.system,
15007
15419
  stopWhen: isStepCount(input.maxSteps ?? 1),
15008
- ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
15420
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {},
15009
15421
  ...input.signal ? { abortSignal: input.signal } : {},
15010
15422
  output,
15011
- providerOptions: toSdkProviderOptions(input.providerOptions)
15423
+ providerOptions: toSdkProviderOptions(
15424
+ mergeProviderOptions(input.providerOptions, effort?.providerOptions)
15425
+ ),
15426
+ ...effort ? { reasoning: effort.reasoning } : {}
15012
15427
  });
15013
15428
  for await (const part of result.fullStream) {
15014
15429
  const typed = part;
@@ -15047,7 +15462,7 @@ var ProviderRouter = class {
15047
15462
  const warnings = toProviderWarnings(raw);
15048
15463
  const applied = input.responseFormat ? didApplyResponseFormat(raw) : void 0;
15049
15464
  this.noteResponseFormat(input, applied ?? true, state);
15050
- this.failures.recordSuccess(ct, cm);
15465
+ this.failures.recordSuccess(this.routeOf(ct, entry));
15051
15466
  yield {
15052
15467
  type: "finish",
15053
15468
  ...usage ? { usage } : {},
@@ -15072,14 +15487,15 @@ var ProviderRouter = class {
15072
15487
  }
15073
15488
  state.ct = plan.ct;
15074
15489
  state.cm = plan.cm;
15490
+ state.entry = plan.entry;
15075
15491
  state.attempt = plan.attempt;
15076
15492
  }
15077
15493
  }
15078
- this.noteFailure(state.ct, state.cm);
15079
- yield {
15080
- type: "error",
15081
- error: t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries })
15082
- };
15494
+ }
15495
+ maxTokensFor(input, entry) {
15496
+ const cap = entry ? this.entryTable.entries.get(entry)?.maxOutputTokens : void 0;
15497
+ if (!cap) return input.maxTokens;
15498
+ return input.maxTokens ? Math.min(input.maxTokens, cap) : cap;
15083
15499
  }
15084
15500
  async backoff(n) {
15085
15501
  await new Promise(
@@ -15096,24 +15512,22 @@ var ProviderRouter = class {
15096
15512
  return this.selection.provider;
15097
15513
  }
15098
15514
  getApiKeyHint() {
15099
- if (this.selection.provider === this.config.provider.type) {
15100
- const c = this.config.provider.credentials[this.credentialIndex];
15101
- return c ? maskApiKey(c.apiKey) : "none";
15102
- }
15103
- const opts = this.available.get(this.selection.provider);
15104
- return opts ? maskApiKey(opts.apiKey) : "none";
15515
+ const { provider, entry } = this.selection;
15516
+ const opts = this.available.get(this.routeOf(provider, entry));
15517
+ return opts?.apiKey ? maskApiKey(opts.apiKey) : "none";
15105
15518
  }
15106
15519
  getCapability() {
15107
- return getCapability(this.selection.model, this.config.homeDir, this.selection.provider);
15520
+ const { provider, model, entry } = this.selection;
15521
+ return this.capabilityFor(provider, model, entry);
15108
15522
  }
15109
15523
  async probeImageSupport() {
15110
- const { provider, model } = this.selection;
15524
+ const { provider, model, entry } = this.selection;
15111
15525
  const home = this.config.homeDir;
15112
15526
  const cached = readImageProbe(home, provider, model);
15113
15527
  if (cached !== void 0) return cached;
15114
15528
  let lm;
15115
15529
  try {
15116
- lm = await this.getModel(provider, model);
15530
+ lm = await this.getModel(this.routeOf(provider, entry), model);
15117
15531
  } catch {
15118
15532
  return false;
15119
15533
  }
@@ -15134,6 +15548,21 @@ var ProviderRouter = class {
15134
15548
  });
15135
15549
  }
15136
15550
  };
15551
+ function messageTranslator(messages, table) {
15552
+ const cache = /* @__PURE__ */ new Map();
15553
+ return (type, entry) => {
15554
+ const inline = entryAcceptsToolMedia(table, type, entry);
15555
+ let hit = cache.get(inline);
15556
+ if (!hit) {
15557
+ hit = toModelMessages(messages, { toolMediaInline: inline });
15558
+ cache.set(inline, hit);
15559
+ }
15560
+ return hit;
15561
+ };
15562
+ }
15563
+ function entryTried(id) {
15564
+ return `@${id}`;
15565
+ }
15137
15566
  var STATIC_MODEL_CATALOG = {
15138
15567
  openai: [
15139
15568
  "gpt-4o",
@@ -15194,6 +15623,9 @@ var PROVIDER_DISCOVERY = {
15194
15623
  perplexity: { baseUrl: "https://api.perplexity.ai", auth: "bearer" },
15195
15624
  togetherai: { baseUrl: "https://api.together.xyz/v1", auth: "bearer" }
15196
15625
  };
15626
+ function discoverySpec(provider) {
15627
+ return PROVIDER_DISCOVERY[provider];
15628
+ }
15197
15629
  function cachePath(homeDir, provider) {
15198
15630
  return join(homeDir, "cache", `models_${provider}.json`);
15199
15631
  }
@@ -15222,14 +15654,14 @@ function saveCache2(homeDir, provider, models) {
15222
15654
  }
15223
15655
  }
15224
15656
  function modelListCandidates(baseUrl) {
15225
- const base = baseUrl.trim().replace(/\/+$/, "");
15226
- if (!base) return [];
15227
- const alt = base.endsWith("/v1") ? base.slice(0, -"/v1".length) : `${base}/v1`;
15228
- return alt === base ? [base] : [base, alt];
15657
+ const base2 = baseUrl.trim().replace(/\/+$/, "");
15658
+ if (!base2) return [];
15659
+ const alt = base2.endsWith("/v1") ? base2.slice(0, -"/v1".length) : `${base2}/v1`;
15660
+ return alt === base2 ? [base2] : [base2, alt];
15229
15661
  }
15230
- function buildRequest(base, auth, apiKey) {
15662
+ function buildRequest(base2, auth, apiKey) {
15231
15663
  const headers = { Accept: "application/json" };
15232
- let url = `${base}/models`;
15664
+ let url = `${base2}/models`;
15233
15665
  if (auth === "x-api-key") {
15234
15666
  headers["x-api-key"] = apiKey;
15235
15667
  headers["anthropic-version"] = "2023-06-01";
@@ -15252,9 +15684,9 @@ function parseModelList(payload) {
15252
15684
  return [...ids, ...names].filter((v) => typeof v === "string" && v.length > 0).map((id) => id.startsWith("models/") ? id.slice("models/".length) : id);
15253
15685
  }
15254
15686
  async function fetchFromApi(baseUrl, apiKey, auth) {
15255
- for (const base of modelListCandidates(baseUrl)) {
15687
+ for (const base2 of modelListCandidates(baseUrl)) {
15256
15688
  try {
15257
- const { url, headers } = buildRequest(base, auth, apiKey);
15689
+ const { url, headers } = buildRequest(base2, auth, apiKey);
15258
15690
  const resp = await fetch(url, { headers, signal: AbortSignal.timeout(1e4) });
15259
15691
  if (!resp.ok) continue;
15260
15692
  const models = parseModelList(await resp.json());
@@ -15267,8 +15699,8 @@ async function fetchFromApi(baseUrl, apiKey, auth) {
15267
15699
  }
15268
15700
  async function fetchOllamaModels() {
15269
15701
  try {
15270
- const base = process.env.OLLAMA_BASE_URL || "http://localhost:11434";
15271
- const resp = await fetch(`${base}/api/tags`, { signal: AbortSignal.timeout(5e3) });
15702
+ const base2 = process.env.OLLAMA_BASE_URL || "http://localhost:11434";
15703
+ const resp = await fetch(`${base2}/api/tags`, { signal: AbortSignal.timeout(5e3) });
15272
15704
  if (!resp.ok) return [];
15273
15705
  const data = await resp.json();
15274
15706
  return (data.models ?? []).map((m) => m.name).filter((n) => !!n);
@@ -15284,9 +15716,9 @@ async function discoverModels(homeDir, provider, apiKey, baseUrl) {
15284
15716
  }
15285
15717
  if (apiKey) {
15286
15718
  const spec = PROVIDER_DISCOVERY[provider];
15287
- const base = baseUrl?.trim() || spec?.baseUrl || "";
15288
- if (base) {
15289
- const models = await fetchFromApi(base, apiKey, spec?.auth ?? "bearer");
15719
+ const base2 = baseUrl?.trim() || spec?.baseUrl || "";
15720
+ if (base2) {
15721
+ const models = await fetchFromApi(base2, apiKey, spec?.auth ?? "bearer");
15290
15722
  if (models) {
15291
15723
  saveCache2(homeDir, provider, models);
15292
15724
  return { models, source: "live" };
@@ -15304,6 +15736,102 @@ function clearModelCache(homeDir, provider) {
15304
15736
  } catch {
15305
15737
  }
15306
15738
  }
15739
+ // src/provider/entry-probe.ts
15740
+ function listTarget(entry) {
15741
+ if (entry.rawUrl) return void 0;
15742
+ if (entry.provider === "ollama") return void 0;
15743
+ const spec = discoverySpec(entry.provider);
15744
+ const base2 = entry.baseUrl?.trim() || spec?.baseUrl;
15745
+ if (!base2) return void 0;
15746
+ const anthropicLike = entry.provider === "anthropic" || entry.provider === "openai-compatible" && entry.protocol === "anthropic-messages";
15747
+ const auth = anthropicLike ? "x-api-key" : spec?.auth ?? "bearer";
15748
+ return { base: base2, auth };
15749
+ }
15750
+ async function listAt(base2, auth, apiKey) {
15751
+ let last = { models: null };
15752
+ for (const candidate of modelListCandidates(base2)) {
15753
+ try {
15754
+ const { url, headers } = buildRequest(candidate, auth, apiKey);
15755
+ const resp = await fetch(url, { headers, signal: AbortSignal.timeout(1e4) });
15756
+ if (!resp.ok) {
15757
+ last = { models: null, httpStatus: resp.status };
15758
+ if (resp.status === 401 || resp.status === 403) return last;
15759
+ continue;
15760
+ }
15761
+ return { models: parseModelList(await resp.json()), httpStatus: resp.status };
15762
+ } catch {
15763
+ last = { models: null, network: true };
15764
+ }
15765
+ }
15766
+ return last;
15767
+ }
15768
+ async function listEntryModels(entry, apiKey) {
15769
+ const target = listTarget(entry);
15770
+ if (!target) return [];
15771
+ const got = await listAt(target.base, target.auth, apiKey ?? "");
15772
+ return got.models ?? [];
15773
+ }
15774
+ function redact(text, apiKey) {
15775
+ if (!apiKey || apiKey.length < 8) return text;
15776
+ return text.split(apiKey).join(maskApiKey(apiKey));
15777
+ }
15778
+ async function testModelEntry(entry, apiKey) {
15779
+ const started = Date.now();
15780
+ const elapsed = () => Date.now() - started;
15781
+ const target = listTarget(entry);
15782
+ let models = [];
15783
+ let listed = false;
15784
+ if (target) {
15785
+ const got = await listAt(target.base, target.auth, apiKey ?? "");
15786
+ listed = got.models !== null;
15787
+ if (got.httpStatus === 401 || got.httpStatus === 403) {
15788
+ return { code: "auth", models: [], httpStatus: got.httpStatus, latencyMs: elapsed() };
15789
+ }
15790
+ if (got.network) return { code: "network", models: [], latencyMs: elapsed() };
15791
+ if (got.models) {
15792
+ models = got.models;
15793
+ if (models.includes(entry.model)) {
15794
+ return { code: "ok", models, ...httpOf(got), latencyMs: elapsed() };
15795
+ }
15796
+ }
15797
+ }
15798
+ if (!entry.model) {
15799
+ return listed ? { code: "ok", models, latencyMs: elapsed() } : { code: "need-model", models: [], latencyMs: elapsed() };
15800
+ }
15801
+ try {
15802
+ const factory = await createEntryFactory(entry, apiKey);
15803
+ await generateText({
15804
+ model: factory(entry.model),
15805
+ messages: [{ role: "user", content: "ping" }],
15806
+ maxOutputTokens: 1,
15807
+ abortSignal: AbortSignal.timeout(2e4)
15808
+ });
15809
+ return { code: "ok", models, latencyMs: elapsed() };
15810
+ } catch (err) {
15811
+ const status = extractStatus(err);
15812
+ const detail = redact(describeProviderError(err), apiKey);
15813
+ return {
15814
+ code: classify(status, detail),
15815
+ models,
15816
+ ...status ? { httpStatus: status } : {},
15817
+ latencyMs: elapsed(),
15818
+ detail
15819
+ };
15820
+ }
15821
+ }
15822
+ function httpOf(got) {
15823
+ return got.httpStatus ? { httpStatus: got.httpStatus } : {};
15824
+ }
15825
+ function classify(status, detail) {
15826
+ if (status === 401 || status === 403) return "auth";
15827
+ if (status === 404 || /model[^.]*not.?(found|exist)|no such model|does not exist/i.test(detail)) {
15828
+ return "model-not-found";
15829
+ }
15830
+ if (status === void 0 && /fetch failed|ECONNREFUSED|ENOTFOUND|ETIMEDOUT|timeout|aborted/i.test(detail)) {
15831
+ return "network";
15832
+ }
15833
+ return "error";
15834
+ }
15307
15835
  var SETTING_WRITE_LAYERS = ["user", "projectLocal"];
15308
15836
  function isSettingWriteLayer(value) {
15309
15837
  return SETTING_WRITE_LAYERS.includes(value);
@@ -15428,6 +15956,25 @@ function writeSettingValue(req) {
15428
15956
  }
15429
15957
  return req.layer === "user" ? writeUserYaml(req.path, req.key, value) : writeSettingsJson(req.path, req.key, value);
15430
15958
  }
15959
+ var UNSETTABLE_USER_KEYS = ["models.utility", "fallback.model"];
15960
+ function unsetUserSetting(path, key) {
15961
+ const text = existsSync(path) ? readFileSync(path, "utf-8") : "";
15962
+ const before = asMapping(parseYaml(text));
15963
+ if (before === null) return { ok: false, reason: "unwritable-file" };
15964
+ const dot = key.indexOf(".");
15965
+ const section = key.slice(0, dot);
15966
+ const field = key.slice(dot + 1);
15967
+ const beforeSection = section in before ? asMapping(before[section]) : {};
15968
+ if (beforeSection === null) return { ok: false, reason: "unwritable-file" };
15969
+ if (!(field in beforeSection)) return { ok: true, value: "" };
15970
+ const next = unsetSectionField(text, section, field);
15971
+ const after = asMapping(parseYaml(next));
15972
+ if (after === null) return { ok: false, reason: "unwritable-file" };
15973
+ const afterSection = section in after ? asMapping(after[section]) : {};
15974
+ const intact = afterSection !== null && !(field in afterSection) && sameKeysAndValues(before, after, section) && sameKeysAndValues(beforeSection, afterSection, field);
15975
+ if (!intact) return { ok: false, reason: "unwritable-file" };
15976
+ return commit(path, next, 384, "");
15977
+ }
15431
15978
  function writeUserYaml(path, key, value) {
15432
15979
  const text = existsSync(path) ? readFileSync(path, "utf-8") : "";
15433
15980
  const before = asMapping(parseYaml(text));
@@ -18818,7 +19365,7 @@ var GoalService = class {
18818
19365
  }
18819
19366
  };
18820
19367
  // src/goal/provider.ts
18821
- function withGoalPrompt(base, view) {
19368
+ function withGoalPrompt(base2, view) {
18822
19369
  const decorate = (input) => {
18823
19370
  const now = view();
18824
19371
  if (!now) return input;
@@ -18827,9 +19374,9 @@ function withGoalPrompt(base, view) {
18827
19374
 
18828
19375
  ${block}` } : input;
18829
19376
  };
18830
- const wrapped = Object.create(base);
18831
- wrapped.generate = (input) => base.generate(decorate(input));
18832
- wrapped.stream = (input) => base.stream(decorate(input));
19377
+ const wrapped = Object.create(base2);
19378
+ wrapped.generate = (input) => base2.generate(decorate(input));
19379
+ wrapped.stream = (input) => base2.stream(decorate(input));
18833
19380
  return wrapped;
18834
19381
  }
18835
19382
  // src/goal/engine.ts
@@ -19105,6 +19652,7 @@ function isLastOpenSession(value) {
19105
19652
  const v = value;
19106
19653
  return typeof v["root"] === "string" && v["root"].length > 0 && typeof v["sessionId"] === "string" && v["sessionId"].length > 0 && typeof v["at"] === "number";
19107
19654
  }
19655
+ var isWidthShaped = (value) => typeof value === "number" && Number.isInteger(value) && value > 0;
19108
19656
  var FILE_VERSION4 = 1;
19109
19657
  function describe9(err) {
19110
19658
  return err instanceof Error ? err.message : String(err);
@@ -19127,7 +19675,10 @@ var AppearanceStore = class {
19127
19675
  const value = patch[key];
19128
19676
  if (typeof value === "string" && value.length > 0) next[key] = value;
19129
19677
  }
19130
- if (next.theme === this.prefs.theme && next.lang === this.prefs.lang) return;
19678
+ if (isWidthShaped(patch.sidebarWidth)) next.sidebarWidth = patch.sidebarWidth;
19679
+ if (next.theme === this.prefs.theme && next.lang === this.prefs.lang && next.sidebarWidth === this.prefs.sidebarWidth) {
19680
+ return;
19681
+ }
19131
19682
  this.prefs = next;
19132
19683
  this.save();
19133
19684
  }
@@ -19151,6 +19702,7 @@ var AppearanceStore = class {
19151
19702
  if (file.version !== FILE_VERSION4) return;
19152
19703
  if (typeof file.theme === "string" && file.theme.length > 0) this.prefs.theme = file.theme;
19153
19704
  if (typeof file.lang === "string" && file.lang.length > 0) this.prefs.lang = file.lang;
19705
+ if (isWidthShaped(file.sidebarWidth)) this.prefs.sidebarWidth = file.sidebarWidth;
19154
19706
  }
19155
19707
  save() {
19156
19708
  const body2 = JSON.stringify(
@@ -19333,9 +19885,9 @@ function matchPreauthorization(policy, target) {
19333
19885
  }
19334
19886
  return null;
19335
19887
  }
19336
- function mergeHeadlessPolicy(base, override) {
19888
+ function mergeHeadlessPolicy(base2, override) {
19337
19889
  return {
19338
- ...base,
19890
+ ...base2,
19339
19891
  ...override?.allowTools ? { allowTools: override.allowTools } : {},
19340
19892
  ...override?.allowOperations ? { allowOperations: override.allowOperations } : {}
19341
19893
  };
@@ -21091,14 +21643,14 @@ var TextGuard = class {
21091
21643
  }
21092
21644
  consume(unit) {
21093
21645
  const hits = this.host.scan(this.host.skipCodeBlocks ? this.blankCode(unit) : unit);
21094
- const base = this.consumed;
21646
+ const base2 = this.consumed;
21095
21647
  this.consumed += unit.length;
21096
21648
  if (hits.length === 0) {
21097
21649
  this.out += unit;
21098
21650
  return unit;
21099
21651
  }
21100
21652
  for (const hit of hits) {
21101
- this.hits.push({ ...hit, start: hit.start + base, end: hit.end + base });
21653
+ this.hits.push({ ...hit, start: hit.start + base2, end: hit.end + base2 });
21102
21654
  }
21103
21655
  const action = strongestOf(hits);
21104
21656
  if (action === "block") {
@@ -21723,7 +22275,7 @@ function loadLexiconDir(dir, source) {
21723
22275
  }
21724
22276
  const out = { rules: [], exempt: [], issues: [] };
21725
22277
  for (const file of files.sort()) {
21726
- const spec = classify(file);
22278
+ const spec = classify2(file);
21727
22279
  if (!spec) {
21728
22280
  out.issues.push({
21729
22281
  path: file,
@@ -21747,14 +22299,14 @@ function loadLexiconDir(dir, source) {
21747
22299
  }
21748
22300
  return out;
21749
22301
  }
21750
- function classify(file) {
21751
- const base = file.slice(0, -".txt".length);
21752
- if (base === "exempt") return { kind: "exempt" };
21753
- if (base.endsWith(".regex")) {
21754
- const category = base.slice(0, -".regex".length);
22302
+ function classify2(file) {
22303
+ const base2 = file.slice(0, -".txt".length);
22304
+ if (base2 === "exempt") return { kind: "exempt" };
22305
+ if (base2.endsWith(".regex")) {
22306
+ const category = base2.slice(0, -".regex".length);
21755
22307
  return CATEGORY_SET.has(category) ? { kind: "regex", category } : null;
21756
22308
  }
21757
- return CATEGORY_SET.has(base) ? { kind: "terms", category: base } : null;
22309
+ return CATEGORY_SET.has(base2) ? { kind: "terms", category: base2 } : null;
21758
22310
  }
21759
22311
  function collect3(content, file, spec, source, out) {
21760
22312
  const lines = content.split("\n");
@@ -22853,8 +23405,8 @@ function copyTree(from, to, label, budget, issues, lang) {
22853
23405
  return left;
22854
23406
  }
22855
23407
  function defaultName(source) {
22856
- const base = basename(source);
22857
- return extname(base).toLowerCase() === ".md" ? base.slice(0, -3) : base;
23408
+ const base2 = basename(source);
23409
+ return extname(base2).toLowerCase() === ".md" ? base2.slice(0, -3) : base2;
22858
23410
  }
22859
23411
  function skillSegmentIssue(segment, lang) {
22860
23412
  if (segment.length === 0) return t("skill.import_bad_name_empty", void 0, lang);
@@ -23037,8 +23589,8 @@ function loadOne(mdFile, catPath, category, dirName, spec, skills, issues) {
23037
23589
  const parsed = parseSkillFrontmatter(raw, label);
23038
23590
  issues.push(...parsed.issues);
23039
23591
  const declared = parsed.fm.name?.trim();
23040
- const base = declared ? declared : dirName;
23041
- const name = spec.prefix ? `${spec.prefix}:${base}` : base;
23592
+ const base2 = declared ? declared : dirName;
23593
+ const name = spec.prefix ? `${spec.prefix}:${base2}` : base2;
23042
23594
  const previous = skills.get(name);
23043
23595
  if (previous && previous.scope !== scope) {
23044
23596
  issues.push({
@@ -23195,9 +23747,9 @@ var SkillSystem = class {
23195
23747
  const indexName = declared ? declared : name;
23196
23748
  if (this.skills.has(indexName)) throw new Error(`Skill "${indexName}" already exists`);
23197
23749
  const dir = join(this.skillsDir, category, indexName);
23198
- const base = resolve(this.skillsDir);
23750
+ const base2 = resolve(this.skillsDir);
23199
23751
  const resolved = resolve(dir);
23200
- if (resolved === base || !resolved.startsWith(base + sep)) {
23752
+ if (resolved === base2 || !resolved.startsWith(base2 + sep)) {
23201
23753
  throw new Error(
23202
23754
  `Skill name or category escapes the skills directory: ${category}/${indexName}`
23203
23755
  );
@@ -23246,12 +23798,12 @@ var SkillSystem = class {
23246
23798
  this.invalidateCache();
23247
23799
  }
23248
23800
  resolveInsideSkill(skillPath, relPath) {
23249
- const base = resolve(skillPath);
23801
+ const base2 = resolve(skillPath);
23250
23802
  const resolved = resolve(join(skillPath, relPath));
23251
- if (resolved !== base && !resolved.startsWith(base + sep)) {
23803
+ if (resolved !== base2 && !resolved.startsWith(base2 + sep)) {
23252
23804
  throw new Error("Path traversal denied");
23253
23805
  }
23254
- const realBase = realpathSync(base);
23806
+ const realBase = realpathSync(base2);
23255
23807
  const realTarget = realpathOfDeepestExisting(resolved);
23256
23808
  if (realTarget !== realBase && !realTarget.startsWith(realBase + sep)) {
23257
23809
  throw new Error("Path traversal denied");
@@ -24275,7 +24827,7 @@ var ContextCompressor = class {
24275
24827
  }
24276
24828
  async generateSummary(middleText, instruction, plan, goal) {
24277
24829
  try {
24278
- const base = this.lastSummary ? `Update the following summary with new conversation content. Keep the same structure.
24830
+ const base2 = this.lastSummary ? `Update the following summary with new conversation content. Keep the same structure.
24279
24831
 
24280
24832
  ## Previous Summary
24281
24833
  ${this.lastSummary}
@@ -24287,9 +24839,9 @@ ${SUMMARY_TEMPLATE}
24287
24839
 
24288
24840
  ## Conversation
24289
24841
  ${middleText}`;
24290
- const withPlan = plan?.trim() ? `${base}
24842
+ const withPlan = plan?.trim() ? `${base2}
24291
24843
 
24292
- ${planSection(plan.trim())}` : base;
24844
+ ${planSection(plan.trim())}` : base2;
24293
24845
  const withGoal = goal?.trim() ? `${withPlan}
24294
24846
 
24295
24847
  ${goalSection(goal.trim())}` : withPlan;
@@ -24359,10 +24911,10 @@ var SCOPES = ["directory", "directory-tree"];
24359
24911
  function describe14(err) {
24360
24912
  return err instanceof Error ? err.message : String(err);
24361
24913
  }
24362
- function covers(base, target, scope) {
24363
- if (samePath(base, target)) return true;
24914
+ function covers(base2, target, scope) {
24915
+ if (samePath(base2, target)) return true;
24364
24916
  if (scope === "directory") return false;
24365
- const rel = relative(base, target);
24917
+ const rel = relative(base2, target);
24366
24918
  return !rel.startsWith("..") && !isAbsolute(rel);
24367
24919
  }
24368
24920
  var TrustManager = class {
@@ -24378,10 +24930,10 @@ var TrustManager = class {
24378
24930
  let bestLen = -1;
24379
24931
  let best;
24380
24932
  for (const record of this.records) {
24381
- const base = resolve(record.path);
24382
- if (!covers(base, target, record.scope)) continue;
24383
- if (base.length > bestLen || base.length === bestLen && record.level === "untrusted") {
24384
- bestLen = base.length;
24933
+ const base2 = resolve(record.path);
24934
+ if (!covers(base2, target, record.scope)) continue;
24935
+ if (base2.length > bestLen || base2.length === bestLen && record.level === "untrusted") {
24936
+ bestLen = base2.length;
24385
24937
  best = record;
24386
24938
  }
24387
24939
  }
@@ -24877,4 +25429,4 @@ function trunc(stdout, stderr) {
24877
25429
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
24878
25430
  */
24879
25431
 
24880
- export { ABOUT_DESC_MAX_CHARS, ABOUT_README_MAX_CHARS, AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, AppearanceStore, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_ICON_MAX_BYTES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_ASSET_DIRS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_FILE, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, approvalEntryFor, approvalRootFor, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, defaultModelOf, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, grantedPathsFor, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidInterval, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelListCandidates, needsAllowlist, nextFallbackModel, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, normalizeTrigger, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readImageSize, readMarketplaces, readPluginAbout, readPluginIcon, readPluginManifest, readPluginRecords, readProviderKeyEnv, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };
25432
+ export { ABOUT_DESC_MAX_CHARS, ABOUT_README_MAX_CHARS, AGENT_ROLE_DIAG_MODULE, ARTIFACT_MAX_TOTAL_BYTES, ARTIFACT_RETENTION_DEFAULTS, ATTACHMENT_MAX_TOTAL_BYTES, ATTACHMENT_RETENTION_DEFAULTS, AgentLoop, AppearanceStore, ApprovalCache, BLOCKED_TOOLS, BudgetGuard, BudgetStore, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, CheckpointManager, CheckpointStore, CodeSandbox, ComplianceEngine, ContextCompressor, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, DelegateManager, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, FTS_TABLES, FTS_TRIGGERS, GOAL_BLOCK_CODES, GoalService, GoalStore, HEADLESS_AUDIT_CODES, HookManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, ImageLoadError, ImportTrustStore, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, LastOpenSessions, MARKETPLACE_DISPLAY_FIELDS, MARKETPLACE_FILE, MAX_AUDIT_ENTRIES, MAX_COMMAND_DEPTH, MAX_GOAL_MAX_ROUNDS, MAX_IMPORT_DEPTH, MAX_LAST_OPEN_SESSIONS, MAX_OBJECTIVE_CHARS, MAX_REASON_CHARS, MAX_RECENT_WORKSPACES, MODEL_ENTRIES_FILE, MODEL_GIVE_UP_THRESHOLD, MULTIMODAL_DEFAULTS, ManagedPolicyError, ManagedSettingsSchema, MemoryManager, ModelFailureTracker, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, PACK_EXCLUDES, PACK_MAX_BYTES, PACK_MAX_FILES, PERMISSION_AUDIT_CODES, PLUGIN_ICON_MAX_BYTES, PLUGIN_LAYOUT, PLUGIN_MANIFEST_FILE, PLUGIN_MANIFEST_KEYS, PLUGIN_NAME_PATTERN, PLUGIN_ROOT_ENV, PLUGIN_ROOT_TOKEN, PLUGIN_VISIBILITY, POLICY_DECISIONS, PROBE_VERSION, PROJECT_AGENTS_SUBDIR, PROVIDER_NO_CREDENTIAL, PermissionManager, PlanModeState, PluginAuthorSchema, PluginStore, PolicyRuleSchema, ProviderError, ProviderRouter, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, RecentWorkspaces, SANDBOX_COVERS, SANDBOX_EXCLUDES, SCALAR_KEYS, SCHEDULE_DEFAULTS, SCHEDULE_DEFAULT_ALLOWLIST, SCHEDULE_FORM_BUDGET_USD, SCHEDULE_ISSUE_CODES, SCHTASKS_FOLDER, SESSION_MESSAGES_READ_TOOL, SESSION_MESSAGES_SEARCH_TOOL, SESSION_QUERY_TOOLS, SESSION_SEARCH_TOOL, SESSION_TRACE_TOOL, SETTINGS_LAYER_ORDER, SETTING_WRITE_KEYS, SETTING_WRITE_LAYERS, SKILL_ASSET_DIRS, SKILL_CREATE_TOOL, SKILL_EDIT_TOOL, SKILL_FILE, SKILL_PATCH_TOOL, SKILL_VIEW_TOOL, SKILL_WRITE_FILE_TOOL, SLASH_COMMANDS_DIAG_MODULE, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, SessionManager, SessionQueries, SessionReferences, SettingsFileError, SettingsFileSchema, SkillLearner, SkillSystem, TOOL_SEARCH_TOOL, TaskNoticeTracker, ToolRegistry, TrackerDB, TrustManager, UNSETTABLE_USER_KEYS, Workspace, acquireScheduleLock, addMarketplace, addUsage, admitArtifact, admitArtifacts, affectedPaths, approvalEntryFor, approvalRootFor, artifactLaunchArgv, artifactOpenRefusal, assertLevelAllowed, blockedRoleTools, buildContextBreakdown, buildFireArgv, buildFtsMatchQuery, buildSchemaExports, buildSystemPrompt, buildTrigramMatchQuery, builtinRoles, capabilityOfRef, checkEpochVersion, checkFtsHealth, checkShadowing, classifyPersistFailure, classifyProviderError, clearModelCache, coalesceRecording, codeModeWorkerPath, commandNameFromRelPath, compileRuleLists, compressionOptions, computeCost, computeCostMicros, computeExecHash, countRules, createAllowAllGate, createAllowAllImportGate, createAskQuestionTool, createCodeExecTool, createDelegateTool, createDenyAllGate, createDenyAllImportGate, createGoalTool, createImportGate, createMemoryTool, createPlanModeTools, createRoleFile, createRoleScope, createRunCodeTool, createScheduleDraftTool, createSessionMessagesReadTool, createSessionMessagesSearchTool, createSessionSearchTool, createSessionSearchTools, createSessionTraceTool, createSkillViewTool, createSkillWriteTools, createTodoTool, createToolRevealState, createToolScope, createToolSearchTool, createTrustGate, currentEpochVersion, decideImport, decideTrust, defaultConfig, describeArtifactRefusal, describeBackgroundTask, describeHeadlessPolicy, describeIsolation, describeJsonError, diagnoseSchedules, discoverMarkdown, discoverModels, discoverProject, discoverySpec, displayAllows, displayCurrency, draftCommandLine, draftWidening, escapeRuleContent, estimateImageTokens, estimateRunTokens, estimateTokens, evaluateSelection, expandCommand, expandImports, explainSetting, extractArchive, findInstructionFiles, findProjectInstructions, findRole, formatBytes, formatSkillIndex, formatUsd, gateForTool, getCapability, getConfigIssues, getConfigOrigins, getImportGate, getTrustGate, grantedPathsFor, hasDedicatedExecContent, hasExecTierContent, hasPromptCaching, hasProviderCredentials, hasReadTierContent, hasUsableModel, imageFromBase64, imageFromPath, imagePartTokens, importParts, importSkills, installPlugin, instructionDirs, instructionNotices, intervalSlots, inventoryProjectExtensions, inventoryTotal, isBlockCode, isCodeModeDeferred, isEmptyRules, isGoalPhase, isNonInteractive, isReadOnlySubCall, isSettingWriteLayer, isTransientPersistFailure, isValidInterval, isValidPluginName, isWithinWindow, isWritableSettingKey, labelSlug, layerRank, listEntryModels, listRunningSchedules, listSessionCandidates, listWorkspaceFiles, loadCommandDefinitions, loadCommandFile, loadCommands, loadConfig, loadHookSources, loadKeybindings, loadLexiconDir, loadLexiconFiles, loadModelEntries, loadPlugins, loadPolicyFiles, loadPolicyRules, loadRoleDefinitions, loadRoleDir, loadedImports, localDateString, managedSwitchNotes, mapClaudeToolName, mapEpochToolName, mapOperationType, matchPreauthorization, matchRuleLists, matchesHook, mentionsPluginRoot, mergeHeadlessPolicy, mergeRoles, mergeSchedulePatch, microsToUsd, modelEntriesPath, modelListCandidates, needsAllowlist, nextRunAt, noConfigOrigins, noManagedPolicy, noPlugins, normalizeModelEntry, normalizeTrigger, openArtifact, originOf, packFileName, packPlugin, parseClaudeSettingsHooks, parseClock, parseDate, parseFrontmatter, parseHooksConfig, parseHooksConfigVerbose, parseImportLine, parseModelEntry, parseModelList, parseRoleDefinition, parseScheduleDraft, parseSkillFrontmatter, parseTrigger, pendingImageTokens, persistFailureCode, persistParts, persistToolResults, persistedUsage, planPack, previewSkillImport, pricingFor, projectEntryForDisplay, pruneArtifacts, pruneAttachments, pruneRecordings, pruneSchedules, readEntryKey, readImageSize, readMarketplaces, readPluginAbout, readPluginIcon, readPluginManifest, readPluginRecords, readRecording, readSessionSurface, reclaimFtsSpace, redactLine, refreshOpenRouterMetadata, removeMarketplace, removeRecordings, removeRoleFile, removeSessionArtifacts, removeSessionAttachments, renderImportTree, renderProjectContext, renderSdkDeclaration, renderSelfIndex, repairSchedules, resolveArtifactRetention, resolveCodeModeWorker, resolveComplianceDirs, resolveEntrySource, resolveInWorkspace, resolveMarketplaceRef, resolveMentions, resolveMultimodalPolicy, resolvePolicyDirs, resolvePricingAt, resolveProjectRoot, resolveScheduleBackend, resolveSessionReference, resolveSessionVisibility, resolveSettings, resolveSkillDirs, resolveSource, resolveTar, resolveWorkspace, restoreParts, revealScopedProvider, roleScopedProvider, roleSourceLabel, ruleCovers, ruleFromString, ruleToString, runCode, runCommand2 as runCommand, sanitizeArgNames, sanitizeCommand, sanitizeFtsQuery, sanitizePath, sanitizeToolOutput, saveModelEntries, scanInstructions, scanPluginDir, scanSkillDirs, searchMarketplaces, setDisplayCurrency, setImportGate, setPluginEnabled, setTrustGate, settingValueChoices, settingValueKind, settingWriteApply, settingWriteLayers, settingWriteTargets, skillIndexDescription, skillIndexResidency, skillIndexStats, sniffMediaType, splitFrontmatter, staticToolProvider, stripArtifactData, substitutePluginRoot, suggestRuleFromApproval, systemOpenArgv, systemPromptSegments, testModelEntry, toPosix, toolDefinitionTokens, toolMatches, toolTableTokens, totalTokens, traceSession, triggerPeriodMs, unescapeRuleContent, uninstallPlugin, unknownRoleSkills, unknownRoleTools, unloadedInstructionFiles, unsetUserSetting, updateMarketplace, updatePlugin, usdToMicros, validateSchedule, validateTrigger, validateWindow, withGoalCompression, withGoalPrompt, workspaceDisplayPath, writeSettingValue };