@epoch-agent/core 1.1.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 +1501 -1157
  3. package/dist/index.js +1109 -563
  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, BRAND_WHEN_SIDEBAR_COLLAPSED, 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({}),
@@ -13187,13 +13372,15 @@ var EpochConfigSchema = z.object({
13187
13372
  display: DisplaySchema2.optional(),
13188
13373
  brand: z.string().min(1).optional(),
13189
13374
  brandWhenSidebarCollapsed: z.enum(BRAND_WHEN_SIDEBAR_COLLAPSED).optional(),
13375
+ brandMenuId: z.string().min(1).optional(),
13190
13376
  sidebarMenu: z.array(SidebarNavItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13191
13377
  message: "sidebarMenu: id must be unique"
13192
13378
  }).refine((items) => items.filter((item) => item.primary === true).length <= 1, {
13193
13379
  message: "sidebarMenu: at most one item can be primary"
13194
13380
  }).optional(),
13195
- userMenu: z.array(SidebarMenuItemSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13196
- 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"
13197
13384
  }).optional(),
13198
13385
  blankCards: z.array(BlankCardSchema).min(1).refine((items) => new Set(items.map((item) => item.id)).size === items.length, {
13199
13386
  message: "blankCards: id must be unique"
@@ -13277,7 +13464,7 @@ function describeJsonError(err, text) {
13277
13464
  const reason9 = message.replace(/\s*in JSON at position \d+.*$/, "");
13278
13465
  return t("config_load.json_at", { reason: reason9, line, column });
13279
13466
  }
13280
- function readSettingsFile(path, schema, known, empty, issues) {
13467
+ function readSettingsFile(path, schema, known, empty, issues, sanitize = (raw) => raw) {
13281
13468
  if (!existsSync(path)) return void 0;
13282
13469
  const text = readFileSync(path, "utf-8");
13283
13470
  let raw;
@@ -13288,11 +13475,27 @@ function readSettingsFile(path, schema, known, empty, issues) {
13288
13475
  issues.push({ path, message: t("config_load.json_invalid", { reason: reason9 }) });
13289
13476
  return { failed: reason9 };
13290
13477
  }
13291
- issues.push(...atFile(path, unknownKeyIssues(known, raw)));
13292
- 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);
13293
13481
  issues.push(...atFile(path, parsed.issues));
13294
13482
  return { file: parsed.value };
13295
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
+ }
13296
13499
  // src/config/managed.ts
13297
13500
  var ManagedSettingsSchema = SettingsFileSchema.extend({
13298
13501
  allowManagedPermissionRulesOnly: z.boolean().optional(),
@@ -13318,7 +13521,8 @@ function readManagedSettings(path, issues) {
13318
13521
  ManagedSettingsSchema,
13319
13522
  MANAGED_KEYS,
13320
13523
  {},
13321
- issues
13524
+ issues,
13525
+ stripLegacyModelValue
13322
13526
  );
13323
13527
  if (!read || "failed" in read) return { policy: noManagedPolicy(path), read };
13324
13528
  return { policy: policyOf(path, read.file), read };
@@ -13378,11 +13582,11 @@ function countRules(lists) {
13378
13582
  if (!lists) return 0;
13379
13583
  return BUCKETS2.reduce((sum, b) => sum + (lists[b]?.length ?? 0), 0);
13380
13584
  }
13381
- function mergeLists(base, add) {
13585
+ function mergeLists(base2, add) {
13382
13586
  const out = {};
13383
13587
  for (const bucket of BUCKETS2) {
13384
- const merged = [...base[bucket] ?? [], ...add[bucket] ?? []];
13385
- 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;
13386
13590
  out[bucket] = [...new Set(merged)];
13387
13591
  }
13388
13592
  return out;
@@ -13413,7 +13617,8 @@ function loadLayer(source, execTrusted, issues) {
13413
13617
  SettingsFileSchema,
13414
13618
  SETTINGS_KEYS,
13415
13619
  {},
13416
- issues
13620
+ issues,
13621
+ stripLegacyModelValue
13417
13622
  );
13418
13623
  return finishLayer(source, read, execTrusted, issues);
13419
13624
  }
@@ -13456,8 +13661,8 @@ function describe6(layer, gated, lists) {
13456
13661
  const parts = BUCKETS2.map((b) => `${b} ${lists[b]?.length ?? 0}`).join(" / ");
13457
13662
  return t("config_load.loaded", { label, parts });
13458
13663
  }
13459
- function resolveSettings(base, projectRoot, execTrusted, opts = {}) {
13460
- const config = structuredClone(base);
13664
+ function resolveSettings(base2, projectRoot, execTrusted, opts = {}) {
13665
+ const config = structuredClone(base2);
13461
13666
  const issues = [];
13462
13667
  const layers = [];
13463
13668
  let lists = {};
@@ -13486,12 +13691,19 @@ function resolveSettings(base, projectRoot, execTrusted, opts = {}) {
13486
13691
  lists = managed.policy.allowManagedPermissionRulesOnly ? managed.outcome.lists : mergeLists(lists, managed.outcome.lists);
13487
13692
  layers.push({ ...managed.outcome, scalars: applyScalars(config, managed.file) });
13488
13693
  }
13489
- if (countRules(lists) > 0 || base.permissions !== void 0) config.permissions = lists;
13694
+ if (countRules(lists) > 0 || base2.permissions !== void 0) config.permissions = lists;
13490
13695
  return { config, layers, issues, managed: managed.policy };
13491
13696
  }
13492
13697
  function loadPluginLayer(entry, issues) {
13493
13698
  const { plugin, path } = entry;
13494
- 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
+ );
13495
13707
  const label = t("config_load.layer_plugin_named", { plugin });
13496
13708
  if (!read) return { layer: "plugin", path, status: "missing", lists: {}, scalars: {} };
13497
13709
  if ("failed" in read) {
@@ -13567,11 +13779,11 @@ function originOf(origins, key) {
13567
13779
  if (origins.file.has(key)) return "user";
13568
13780
  return "builtin";
13569
13781
  }
13570
- function explainSetting(key, base, file) {
13571
- const baseLayer = baseLayerOf(base);
13782
+ function explainSetting(key, base2, file) {
13783
+ const baseLayer = baseLayerOf(base2);
13572
13784
  const steps = [
13573
- step("builtin", base.builtin),
13574
- baseLayer === "builtin" ? { layer: "user" } : step(baseLayer, base.value)
13785
+ step("builtin", base2.builtin),
13786
+ baseLayer === "builtin" ? { layer: "user" } : step(baseLayer, base2.value)
13575
13787
  ];
13576
13788
  if (file) {
13577
13789
  for (const layer of SETTINGS_LAYER_ORDER) {
@@ -13588,10 +13800,10 @@ function explainSetting(key, base, file) {
13588
13800
  chain: file ? steps : []
13589
13801
  };
13590
13802
  }
13591
- function baseLayerOf(base) {
13592
- if (base.origin === "env") return "env";
13593
- if (base.origin === "user") return "user";
13594
- 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";
13595
13807
  }
13596
13808
  function valueOf(file, layer) {
13597
13809
  return file.layers.find((l) => l.layer === layer)?.scalars[file.key];
@@ -13641,8 +13853,9 @@ function _loadImpl(overrideHomeDir) {
13641
13853
  mkdirSync(homeDir, { recursive: true });
13642
13854
  const configPath2 = getConfigPath(homeDir);
13643
13855
  const { mtime, size } = fileStamp(configPath2);
13856
+ const entriesStamp = stampKey(modelEntriesPath(homeDir));
13644
13857
  const envSnapshot = snapshotEnv();
13645
- 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)) {
13646
13859
  _lastIssues = _cache.issues;
13647
13860
  _lastOrigins = _cache.origins;
13648
13861
  return _cache.config;
@@ -13658,6 +13871,7 @@ function _loadImpl(overrideHomeDir) {
13658
13871
  homeDir,
13659
13872
  mtime,
13660
13873
  size,
13874
+ entriesStamp,
13661
13875
  envSnapshot,
13662
13876
  config: cached.config,
13663
13877
  issues,
@@ -13666,12 +13880,9 @@ function _loadImpl(overrideHomeDir) {
13666
13880
  return cached.config;
13667
13881
  }
13668
13882
  const config = parseRawConfig(read.raw, issues, track);
13669
- const override = readProviderOverride(issues);
13670
- const pinned = applyProviderOverride(config, track, override);
13671
- const secretSource = applySecretsFromEnv(config, homeDir, track);
13672
- if (pinned) applyProviderOverride(config, track, override);
13883
+ reportDroppedEnv(issues);
13673
13884
  applyEnvOverrides(config, issues, track);
13674
- if (secretSource === "config-file" && !pinned) detectProviderFromKey(config);
13885
+ applyModelEntries(config, homeDir, issues);
13675
13886
  if (overrideHomeDir) {
13676
13887
  config.homeDir = overrideHomeDir;
13677
13888
  config.dbPath = dbPath(overrideHomeDir);
@@ -13680,7 +13891,16 @@ function _loadImpl(overrideHomeDir) {
13680
13891
  _lastIssues = issues;
13681
13892
  _lastOrigins = origins;
13682
13893
  _lastKnownGood = { homeDir, config: structuredClone(config), origins };
13683
- _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
+ };
13684
13904
  return config;
13685
13905
  }
13686
13906
  function readRawConfig(configPath2, issues) {
@@ -13706,106 +13926,122 @@ function parseRawConfig(raw, issues, track) {
13706
13926
  const expanded = expandEnvVars(raw);
13707
13927
  const migrated = migrateRawConfig(expanded);
13708
13928
  issues.push(...migrated.issues);
13709
- issues.push(...unknownKeyIssues(CONFIG_KEYS, migrated.raw));
13710
- const outcome = parseLenient(
13711
- EpochConfigSchema,
13712
- dropEmptySections(migrated.raw),
13713
- EpochConfigSchema.parse({})
13714
- );
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({}));
13715
13933
  issues.push(...outcome.issues);
13716
13934
  issues.push(...settingsHiddenIssues(outcome.value));
13717
13935
  issues.push(...settingsPaneBeforeIssues(outcome.value));
13718
- if (outcome.issues.length === 0) collectKeyPaths(migrated.raw, track.file);
13936
+ if (outcome.issues.length === 0) collectKeyPaths(cleaned, track.file);
13719
13937
  return outcome.value;
13720
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
+ }
13721
13990
  function dropEmptySections(raw) {
13722
13991
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return raw;
13723
13992
  const entries2 = Object.entries(raw);
13724
13993
  if (!entries2.some(([, v]) => v === null)) return raw;
13725
13994
  return Object.fromEntries(entries2.filter(([, v]) => v !== null));
13726
13995
  }
13727
- function applySecretsFromEnv(config, homeDir, track) {
13728
- const env = readProviderKeyEnv(homeDir);
13729
- const lookup = (name) => env[name];
13730
- const ownVar = apiKeyEnvVar(config.provider.type);
13731
- const raw = ownVar ? lookup(ownVar) : void 0;
13732
- if (raw) return finishSecrets(config, raw, "explicit", track);
13733
- for (const info of PROVIDER_INFOS) {
13734
- if (!info.apiKeyEnv) continue;
13735
- const value = lookup(info.apiKeyEnv);
13736
- if (!value) continue;
13737
- config.provider.type = info.type;
13738
- track.env.add("provider.type");
13739
- return finishSecrets(config, value, "explicit", track);
13740
- }
13741
- return "config-file";
13742
- }
13743
- function readProviderOverride(issues) {
13744
- const override = {};
13745
- const baseUrl = process.env.EPOCH_BASE_URL;
13746
- if (baseUrl) {
13747
- override.baseUrl = baseUrl;
13748
- override.type = "openai-compatible";
13749
- }
13750
- const type = process.env.EPOCH_PROVIDER;
13751
- if (type) {
13752
- if (isProviderType(type)) {
13753
- override.type ??= type;
13754
- } else {
13755
- issues.push(envIssue("EPOCH_PROVIDER", type, PROVIDER_TYPES.join(" / ")));
13756
- }
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
+ });
13757
14006
  }
13758
- return override;
13759
14007
  }
13760
- function applyProviderOverride(config, track, override) {
13761
- if (override.baseUrl) {
13762
- config.provider.baseUrl = override.baseUrl;
13763
- track.env.add("provider.baseUrl");
13764
- }
13765
- if (!override.type) return false;
13766
- config.provider.type = override.type;
13767
- track.env.add("provider.type");
13768
- return true;
14008
+ function readSecret(name, homeDir) {
14009
+ return process.env[name] || loadEnvFile(getEnvPath(homeDir))[name] || getSecretValues()[name] || void 0;
13769
14010
  }
13770
- function readProviderKeyEnv(homeDir = resolveHomeDir()) {
13771
- const dotEnv = loadEnvFile(getEnvPath(homeDir));
13772
- const fromStore = getSecretValues();
13773
- const result = {};
13774
- for (const name of API_KEY_ENV_VARS) {
13775
- const value = process.env[name] || dotEnv[name] || fromStore[name];
13776
- if (value) result[name] = value;
13777
- }
13778
- return result;
14011
+ function readEntryKey(id, homeDir = resolveHomeDir()) {
14012
+ return readSecret(entrySecretName(id), homeDir);
13779
14013
  }
13780
- function hasProviderCredentials(homeDir = resolveHomeDir()) {
13781
- if (Object.keys(readProviderKeyEnv(homeDir)).length > 0) return true;
13782
- 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
+ );
13783
14019
  }
13784
- function finishSecrets(config, raw, source, track) {
13785
- const first2 = raw.split(",")[0]?.trim();
13786
- if (first2) config.provider.apiKey = first2;
13787
- config.provider.credentials = parseKeys(raw);
13788
- track.env.add("provider.apiKey");
13789
- track.env.add("provider.credentials");
13790
- 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);
13791
14024
  }
13792
14025
  function applyEnvOverrides(config, issues, track) {
13793
14026
  const env = process.env;
13794
14027
  if (env.EPOCH_MODEL) {
13795
- config.model = env.EPOCH_MODEL;
13796
- 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>"));
13797
14032
  }
13798
14033
  if (env.EPOCH_PROFILE) {
13799
14034
  config.profile = env.EPOCH_PROFILE;
13800
14035
  track.env.add("profile");
13801
14036
  }
13802
14037
  if (env.EPOCH_FALLBACK_MODEL) {
13803
- config.fallback = {
13804
- providers: config.fallback?.providers ?? [],
13805
- model: env.EPOCH_FALLBACK_MODEL
13806
- };
13807
- track.env.add("fallback");
13808
- 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
+ }
13809
14045
  }
13810
14046
  if (env.EPOCH_MAX_TURNS) {
13811
14047
  const turns = Number(env.EPOCH_MAX_TURNS);
@@ -13847,11 +14083,6 @@ function applyEnvOverrides(config, issues, track) {
13847
14083
  function envIssue(name, value, expected) {
13848
14084
  return { path: name, message: t("config_load.env_expected", { expected, value }) };
13849
14085
  }
13850
- function detectProviderFromKey(config) {
13851
- const key = config.provider.apiKey;
13852
- if (key.startsWith("sk-ant")) config.provider.type = "anthropic";
13853
- else if (key.startsWith("sk-d")) config.provider.type = "deepseek";
13854
- }
13855
14086
  function backupCorruptConfig(path) {
13856
14087
  try {
13857
14088
  const ts = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
@@ -13864,7 +14095,6 @@ function backupCorruptConfig(path) {
13864
14095
  }
13865
14096
  function snapshotEnv() {
13866
14097
  const keys = [
13867
- ...API_KEY_ENV_VARS,
13868
14098
  "EPOCH_HOME",
13869
14099
  "EPOCH_PROFILE",
13870
14100
  "EPOCH_MODEL",
@@ -13879,8 +14109,6 @@ function snapshotEnv() {
13879
14109
  const snapshot = Object.fromEntries(
13880
14110
  keys.map((k) => [k, process.env[k]])
13881
14111
  );
13882
- const fromStore = getSecretValues();
13883
- for (const name of API_KEY_ENV_VARS) snapshot[`secret:${name}`] = fromStore[name];
13884
14112
  return snapshot;
13885
14113
  }
13886
14114
  function sameEnv(a, b) {
@@ -13920,6 +14148,19 @@ function loadEnvFile(path) {
13920
14148
  return {};
13921
14149
  }
13922
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
+ }
13923
14164
  function fileStamp(path) {
13924
14165
  try {
13925
14166
  const st = statSync(path);
@@ -13928,9 +14169,6 @@ function fileStamp(path) {
13928
14169
  return { mtime: 0, size: 0 };
13929
14170
  }
13930
14171
  }
13931
- function parseKeys(raw) {
13932
- return raw.split(",").map((s) => s.trim()).filter(Boolean).map((k) => ({ apiKey: k }));
13933
- }
13934
14172
  var PROBE_IMAGE_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAIAAAAlC+aJAAAAS0lEQVR42u3PQQkAAAgAsetfWiP4FgYrsKZeS0BAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEDgsqnc8OJg6Ln3AAAAAElFTkSuQmCC";
13935
14173
  var PROBE_PROMPT = "What color is this image? Answer with one word.";
13936
14174
  var MIN_IMAGE_TOKEN_DELTA = 16;
@@ -13968,14 +14206,14 @@ async function runImageProbe(model) {
13968
14206
  };
13969
14207
  }
13970
14208
  const shot = withImage.inputTokens;
13971
- const base = baseline.inputTokens;
13972
- if (shot === void 0 || base === void 0 || shot <= 0 || base <= 0) {
13973
- 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})` };
13974
14212
  }
13975
- const delta = shot - base;
14213
+ const delta = shot - base2;
13976
14214
  return {
13977
14215
  verdict: delta >= MIN_IMAGE_TOKEN_DELTA ? "yes" : "no",
13978
- 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`
13979
14217
  };
13980
14218
  }
13981
14219
  var PROBE_VERSION = 2;
@@ -14014,67 +14252,6 @@ function writeImageProbe(homeDir, provider, model, supportsImages) {
14014
14252
  } catch {
14015
14253
  }
14016
14254
  }
14017
- // src/provider/fallback.ts
14018
- var DEFAULT_MODELS = {
14019
- openai: "gpt-4o-mini",
14020
- anthropic: "claude-haiku-4-5",
14021
- google: "gemini-2.0-flash",
14022
- deepseek: "deepseek-chat",
14023
- groq: "llama-3.3-70b-versatile",
14024
- mistral: "ministral-8b-latest",
14025
- moonshotai: "kimi-latest",
14026
- zhipu: "glm-4.5-flash",
14027
- dashscope: "qwen-flash",
14028
- bytedance: "doubao-seed-2.0-mini",
14029
- qianfan: "ernie-speed-128k",
14030
- cohere: "command-r7b-12-2024",
14031
- xai: "grok-vision-beta",
14032
- perplexity: "sonar",
14033
- "amazon-bedrock": "anthropic.claude-instant-v1",
14034
- azure: "gpt-4o-mini",
14035
- togetherai: "meta-llama/Llama-3.3-70B-Instruct-Turbo",
14036
- ollama: "llama3.2",
14037
- "openai-compatible": "gpt-4o-mini"
14038
- };
14039
- function defaultModelOf(type) {
14040
- return DEFAULT_MODELS[type];
14041
- }
14042
- function modelKey(provider, model) {
14043
- return `${provider}/${model}`;
14044
- }
14045
- function nextFallbackModel(provider, current, tried, configured) {
14046
- for (const candidate of [configured, DEFAULT_MODELS[provider]]) {
14047
- if (!candidate || candidate === current) continue;
14048
- if (tried.has(modelKey(provider, candidate))) continue;
14049
- return candidate;
14050
- }
14051
- return void 0;
14052
- }
14053
- var MODEL_GIVE_UP_THRESHOLD = 3;
14054
- var ModelFailureTracker = class {
14055
- counts = /* @__PURE__ */ new Map();
14056
- abandoned = /* @__PURE__ */ new Set();
14057
- recordFailure(provider, model) {
14058
- const key = modelKey(provider, model);
14059
- if (this.abandoned.has(key)) return false;
14060
- const next = (this.counts.get(key) ?? 0) + 1;
14061
- this.counts.set(key, next);
14062
- if (next < MODEL_GIVE_UP_THRESHOLD) return false;
14063
- this.abandoned.add(key);
14064
- return true;
14065
- }
14066
- recordSuccess(provider, model) {
14067
- this.counts.delete(modelKey(provider, model));
14068
- }
14069
- isAbandoned(provider, model) {
14070
- return this.abandoned.has(modelKey(provider, model));
14071
- }
14072
- clear(provider, model) {
14073
- const key = modelKey(provider, model);
14074
- this.counts.delete(key);
14075
- this.abandoned.delete(key);
14076
- }
14077
- };
14078
14255
  var DEFAULT_CONTEXT_LENGTH2 = 2e5;
14079
14256
  var STATIC_CAPABILITIES = {
14080
14257
  "gpt-4o": {
@@ -14463,6 +14640,9 @@ function openaiCompatibleFactory(name, defaultBaseUrl) {
14463
14640
  });
14464
14641
  };
14465
14642
  }
14643
+ function base(baseUrl) {
14644
+ return baseUrl ? { baseURL: baseUrl } : {};
14645
+ }
14466
14646
  var LOADERS = {
14467
14647
  openai: async ({ apiKey, baseUrl }) => {
14468
14648
  const { createOpenAI } = await import('@ai-sdk/openai');
@@ -14470,29 +14650,29 @@ var LOADERS = {
14470
14650
  if (baseUrl) opts["baseURL"] = baseUrl;
14471
14651
  return createOpenAI(opts);
14472
14652
  },
14473
- anthropic: async ({ apiKey }) => {
14653
+ anthropic: async ({ apiKey, baseUrl }) => {
14474
14654
  const { createAnthropic } = await import('@ai-sdk/anthropic');
14475
- return createAnthropic({ apiKey });
14655
+ return createAnthropic({ apiKey, ...base(baseUrl) });
14476
14656
  },
14477
- google: async ({ apiKey }) => {
14657
+ google: async ({ apiKey, baseUrl }) => {
14478
14658
  const { createGoogleGenerativeAI } = await import('@ai-sdk/google');
14479
- return createGoogleGenerativeAI({ apiKey });
14659
+ return createGoogleGenerativeAI({ apiKey, ...base(baseUrl) });
14480
14660
  },
14481
- deepseek: async ({ apiKey }) => {
14661
+ deepseek: async ({ apiKey, baseUrl }) => {
14482
14662
  const { createDeepSeek } = await import('@ai-sdk/deepseek');
14483
- return createDeepSeek({ apiKey });
14663
+ return createDeepSeek({ apiKey, ...base(baseUrl) });
14484
14664
  },
14485
- groq: async ({ apiKey }) => {
14665
+ groq: async ({ apiKey, baseUrl }) => {
14486
14666
  const { createGroq } = await import('@ai-sdk/groq');
14487
- return createGroq({ apiKey });
14667
+ return createGroq({ apiKey, ...base(baseUrl) });
14488
14668
  },
14489
- mistral: async ({ apiKey }) => {
14669
+ mistral: async ({ apiKey, baseUrl }) => {
14490
14670
  const { createMistral } = await import('@ai-sdk/mistral');
14491
- return createMistral({ apiKey });
14671
+ return createMistral({ apiKey, ...base(baseUrl) });
14492
14672
  },
14493
- moonshotai: async ({ apiKey }) => {
14673
+ moonshotai: async ({ apiKey, baseUrl }) => {
14494
14674
  const { createMoonshotAI } = await import('@ai-sdk/moonshotai');
14495
- return createMoonshotAI({ apiKey });
14675
+ return createMoonshotAI({ apiKey, ...base(baseUrl) });
14496
14676
  },
14497
14677
  zhipu: openaiCompatibleFactory("zhipu", "https://open.bigmodel.cn/api/paas/v4"),
14498
14678
  dashscope: async ({ apiKey, baseUrl }) => {
@@ -14505,17 +14685,17 @@ var LOADERS = {
14505
14685
  },
14506
14686
  bytedance: openaiCompatibleFactory("bytedance", "https://ark.cn-beijing.volces.com/api/v3"),
14507
14687
  qianfan: openaiCompatibleFactory("qianfan", "https://qianfan.baidubce.com/v2"),
14508
- cohere: async ({ apiKey }) => {
14688
+ cohere: async ({ apiKey, baseUrl }) => {
14509
14689
  const { createCohere } = await import('@ai-sdk/cohere');
14510
- return createCohere({ apiKey });
14690
+ return createCohere({ apiKey, ...base(baseUrl) });
14511
14691
  },
14512
- xai: async ({ apiKey }) => {
14692
+ xai: async ({ apiKey, baseUrl }) => {
14513
14693
  const { createXai } = await import('@ai-sdk/xai');
14514
- return createXai({ apiKey });
14694
+ return createXai({ apiKey, ...base(baseUrl) });
14515
14695
  },
14516
- perplexity: async ({ apiKey }) => {
14696
+ perplexity: async ({ apiKey, baseUrl }) => {
14517
14697
  const { createPerplexity } = await import('@ai-sdk/perplexity');
14518
- return createPerplexity({ apiKey });
14698
+ return createPerplexity({ apiKey, ...base(baseUrl) });
14519
14699
  },
14520
14700
  "amazon-bedrock": async ({ apiKey }) => {
14521
14701
  const { createAmazonBedrock } = await import('@ai-sdk/amazon-bedrock');
@@ -14525,13 +14705,13 @@ var LOADERS = {
14525
14705
  const { createAzure } = await import('@ai-sdk/azure');
14526
14706
  return createAzure({ apiKey });
14527
14707
  },
14528
- togetherai: async ({ apiKey }) => {
14708
+ togetherai: async ({ apiKey, baseUrl }) => {
14529
14709
  const { createTogetherAI } = await import('@ai-sdk/togetherai');
14530
- return createTogetherAI({ apiKey });
14710
+ return createTogetherAI({ apiKey, ...base(baseUrl) });
14531
14711
  },
14532
- ollama: async () => {
14712
+ ollama: async ({ baseUrl }) => {
14533
14713
  const { createOllama } = await import('ollama-ai-provider-v2');
14534
- return createOllama();
14714
+ return createOllama(base(baseUrl));
14535
14715
  },
14536
14716
  "openai-compatible": openaiCompatibleFactory("custom", "http://localhost:8080/v1")
14537
14717
  };
@@ -14559,6 +14739,62 @@ async function createModelFactory(type, opts) {
14559
14739
  throw err;
14560
14740
  }
14561
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
+ }
14562
14798
  function isModuleNotFound(err) {
14563
14799
  if (!err || typeof err !== "object") return false;
14564
14800
  const code = err.code;
@@ -14566,21 +14802,127 @@ function isModuleNotFound(err) {
14566
14802
  const msg = err instanceof Error ? err.message : "";
14567
14803
  return /Cannot find (module|package)|Failed to resolve/i.test(msg);
14568
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
+ }
14569
14914
  var CONTEXT_WARN_RATIO = 0.8;
14570
14915
  function fmtTokens(n) {
14571
14916
  return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
14572
14917
  }
14573
14918
  function checkCredential(want, deps) {
14574
14919
  if (deps.hasCredential(want.provider)) return null;
14575
- return { code: "no-credential", provider: want.provider, envVar: apiKeyEnvVar(want.provider) };
14920
+ return { code: "no-credential", entry: want.entry };
14576
14921
  }
14577
14922
  function describeRejection(rejection) {
14578
14923
  switch (rejection.code) {
14579
14924
  case "no-credential":
14580
- return t("provider_switch.no_credential", {
14581
- provider: rejection.provider,
14582
- hint: rejection.envVar ? t("provider_switch.no_credential_env", { envVar: rejection.envVar }) : t("provider_switch.no_credential_plain")
14583
- });
14925
+ return t("provider_switch.no_credential", { entry: rejection.entry });
14584
14926
  case "images-unsupported":
14585
14927
  return t("provider_switch.images_unsupported", { model: rejection.model });
14586
14928
  case "context-overflow":
@@ -14589,6 +14931,8 @@ function describeRejection(rejection) {
14589
14931
  model: rejection.model,
14590
14932
  limit: fmtTokens(rejection.contextLength)
14591
14933
  });
14934
+ case "unknown-entry":
14935
+ return t("provider_switch.unknown_entry", { entry: rejection.entry });
14592
14936
  }
14593
14937
  }
14594
14938
  function describeWarning(warning) {
@@ -14640,15 +14984,13 @@ function evaluateSelection(want, deps, ctx = {}) {
14640
14984
  function reject(rejection) {
14641
14985
  return { ok: false, reason: describeRejection(rejection), rejection };
14642
14986
  }
14643
- // src/provider/types.ts
14644
- var DEFAULT_MODEL = "gpt-4o-mini";
14645
14987
  // src/provider/router.ts
14646
14988
  var ProviderRouter = class {
14647
14989
  config;
14648
14990
  available = /* @__PURE__ */ new Map();
14649
14991
  factories = /* @__PURE__ */ new Map();
14650
- credentialIndex = 0;
14651
- keyEnv;
14992
+ entryTable;
14993
+ effort;
14652
14994
  telemetry = NOOP_TELEMETRY;
14653
14995
  selection;
14654
14996
  configSelection;
@@ -14659,44 +15001,28 @@ var ProviderRouter = class {
14659
15001
  }
14660
15002
  constructor(config) {
14661
15003
  this.config = config;
14662
- this.keyEnv = readProviderKeyEnv();
14663
- this.initFactories(config);
14664
- this.configSelection = {
14665
- provider: config.provider.type,
14666
- model: config.model ?? DEFAULT_MODEL,
14667
- origin: "config"
14668
- };
15004
+ this.entryTable = buildEntryTable(config.modelEntries);
15005
+ this.configSelection = this.resolveConfigured(config);
15006
+ this.initFactories();
14669
15007
  this.selection = { ...this.configSelection };
14670
15008
  }
14671
15009
  getSelection() {
14672
15010
  return { ...this.selection };
14673
15011
  }
14674
15012
  getConfiguredModel() {
14675
- return this.configSelection.model;
15013
+ return entryRef(this.configSelection.entry);
14676
15014
  }
14677
15015
  setSelection(next, ctx = {}) {
14678
- return this.applySelection(
14679
- {
14680
- provider: next.provider ?? this.selection.provider,
14681
- model: next.model,
14682
- origin: "user-switch"
14683
- },
14684
- ctx
14685
- );
15016
+ const want = this.resolveRef(next, "user-switch");
15017
+ return "ok" in want ? want : this.applySelection(want, ctx);
14686
15018
  }
14687
15019
  resetSelection(ctx = {}) {
14688
15020
  return this.applySelection({ ...this.configSelection }, ctx);
14689
15021
  }
14690
15022
  enterTurn(ref, ctx = {}) {
14691
15023
  const previous = { ...this.selection };
14692
- const result = this.applySelection(
14693
- {
14694
- provider: ref.provider ?? this.selection.provider,
14695
- model: ref.model,
14696
- origin: "command-frontmatter"
14697
- },
14698
- ctx
14699
- );
15024
+ const want = this.resolveRef(ref, "command-frontmatter");
15025
+ const result = "ok" in want ? want : this.applySelection(want, ctx);
14700
15026
  return {
14701
15027
  result,
14702
15028
  restore: () => {
@@ -14708,15 +15034,16 @@ var ProviderRouter = class {
14708
15034
  const result = evaluateSelection(
14709
15035
  want,
14710
15036
  {
14711
- hasCredential: (p) => this.available.has(p),
14712
- 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)
14713
15039
  },
14714
15040
  ctx
14715
15041
  );
14716
15042
  if (!result.ok) return result;
14717
15043
  const from = this.selection;
14718
15044
  this.selection = want;
14719
- this.failures.clear(want.provider, want.model);
15045
+ this.effort = void 0;
15046
+ this.failures.clear(this.routeOf(want.provider, want.entry));
14720
15047
  this.telemetry.counter(METRIC.MODEL_SWITCH, 1, {
14721
15048
  "epoch.model.from": modelKey(from.provider, from.model),
14722
15049
  "epoch.model.to": modelKey(want.provider, want.model),
@@ -14724,9 +15051,77 @@ var ProviderRouter = class {
14724
15051
  });
14725
15052
  return result;
14726
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
+ }
14727
15093
  drainNotices() {
14728
15094
  return this.notices.splice(0, this.notices.length);
14729
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
+ }
14730
15125
  getUtilityInfo() {
14731
15126
  const raw = this.config.models?.utility;
14732
15127
  if (!raw) return { selection: this.getSelection(), configured: false };
@@ -14738,149 +15133,129 @@ var ProviderRouter = class {
14738
15133
  issue: t("provider_route.utility_unparsable", { raw })
14739
15134
  };
14740
15135
  }
14741
- const provider = ref.provider ?? this.selection.provider;
14742
- if (!this.available.has(provider)) {
14743
- return {
14744
- selection: this.getSelection(),
14745
- configured: true,
14746
- issue: t("provider_route.utility_no_credential", { provider })
14747
- };
14748
- }
14749
- return {
14750
- selection: { provider, model: ref.model, origin: this.selection.origin },
14751
- configured: true
14752
- };
15136
+ return this.entryUtilityInfo(ref.entry);
14753
15137
  }
14754
- initFactories(config) {
14755
- const haveCred = config.provider.credentials[0]?.apiKey || config.provider.apiKey;
14756
- if (!haveCred) {
14757
- throw new ProviderError(t("provider_route.no_credential"), PROVIDER_NO_CREDENTIAL, false);
14758
- }
14759
- for (const type of PROVIDER_TYPES) {
14760
- const key = this.resolveKey(type, config);
14761
- if (!key) continue;
14762
- const isPrimary = type === config.provider.type;
14763
- this.register(type, key, isPrimary ? config.provider.baseUrl : void 0);
14764
- }
14765
- 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))) {
14766
15142
  throw new ProviderError(
14767
- t("provider_route.provider_no_credential", { provider: config.provider.type }),
15143
+ t("provider_route.entry_no_credential", { entry }),
14768
15144
  PROVIDER_NO_CREDENTIAL,
14769
15145
  false
14770
15146
  );
14771
15147
  }
14772
- if (config.fallback) {
14773
- for (const fbType of config.fallback.providers) {
14774
- if (this.available.has(fbType)) continue;
14775
- const fbKey = this.getFallbackKey(fbType);
14776
- if (fbKey) this.register(fbType, fbKey);
14777
- }
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 });
14778
15155
  }
14779
15156
  }
14780
- register(type, apiKey, baseUrl) {
14781
- this.available.set(type, baseUrl ? { apiKey, baseUrl } : { apiKey });
14782
- 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
+ );
14783
15165
  }
14784
- resolveKey(type, config) {
14785
- if (type === config.provider.type) {
14786
- return config.provider.apiKey || config.provider.credentials[0]?.apiKey;
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
+ );
15175
+ }
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 };
14787
15181
  }
14788
- return this.getFallbackKey(type);
15182
+ return entrySelection(this.entryTable, entry, origin);
14789
15183
  }
14790
- getFallbackKey(type) {
14791
- if (type === "ollama") return "local";
14792
- const envName = apiKeyEnvVar(type);
14793
- if (!envName) return void 0;
14794
- 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 };
14795
15202
  }
14796
- async getModel(type, modelId) {
14797
- const cached = this.factories.get(type);
15203
+ async getModel(route, modelId) {
15204
+ const cached = this.factories.get(route);
14798
15205
  if (cached) return cached(modelId ?? this.selection.model);
14799
- const opts = this.available.get(type);
15206
+ const opts = this.available.get(route);
14800
15207
  if (!opts) {
14801
- 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
+ );
14802
15213
  }
14803
15214
  let factory;
14804
15215
  try {
14805
- factory = await createModelFactory(type, opts);
15216
+ factory = opts.entry ? await createEntryFactory(opts.entry, opts.apiKey || void 0) : await createModelFactory(route, opts);
14806
15217
  } catch (err) {
14807
15218
  throw new ProviderError(err instanceof Error ? err.message : String(err), 1005, false);
14808
15219
  }
14809
- this.factories.set(type, factory);
15220
+ this.factories.set(route, factory);
14810
15221
  return factory(modelId ?? this.selection.model);
14811
15222
  }
14812
- rotateCredential() {
14813
- const creds = this.config.provider.credentials;
14814
- if (this.credentialIndex + 1 < creds.length) {
14815
- this.credentialIndex++;
14816
- const newCred = creds[this.credentialIndex];
14817
- if (!newCred) return false;
14818
- this.register(this.config.provider.type, newCred.apiKey, this.config.provider.baseUrl);
14819
- return true;
14820
- }
14821
- return false;
14822
- }
14823
- fallbackProvider(currentType, tried) {
14824
- if (!this.config.fallback) return null;
14825
- for (const fbType of this.config.fallback.providers) {
14826
- if (fbType === currentType) continue;
14827
- if (tried?.has(modelKey(fbType, defaultModelOf(fbType)))) continue;
14828
- if (this.available.has(fbType)) {
14829
- this.telemetry.counter(METRIC.PROVIDER_FALLBACK, 1, {
14830
- "epoch.provider.from": currentType,
14831
- "epoch.provider.to": fbType
14832
- });
14833
- return {
14834
- from: currentType,
14835
- fromModel: this.getModelId(),
14836
- to: fbType,
14837
- toModel: defaultModelOf(fbType),
14838
- reason: t("provider_route.fallback_reason")
14839
- };
14840
- }
14841
- }
14842
- return null;
14843
- }
14844
15223
  startPoint(input) {
14845
15224
  if (input.responseFormat && input.tools?.length) {
14846
15225
  throw new ProviderError(t("provider.response_format_with_tools"), 1002, false);
14847
15226
  }
14848
- const ct = input.provider ?? this.selection.provider;
14849
- if (input.provider && !this.available.has(input.provider)) {
14850
- throw new ProviderError(
14851
- t("provider_route.cannot_use", { provider: input.provider }),
14852
- 1001,
14853
- false
14854
- );
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);
14855
15232
  }
14856
- const wanted = input.model ?? (input.provider && input.provider !== this.selection.provider ? defaultModelOf(input.provider) : this.selection.model);
14857
- const cm = input.model ? wanted : this.avoidAbandoned(ct, wanted);
14858
- 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
+ };
14859
15241
  }
14860
- avoidAbandoned(ct, cm) {
14861
- if (!this.failures.isAbandoned(ct, cm)) return cm;
14862
- const seen = /* @__PURE__ */ new Set([modelKey(ct, cm)]);
14863
- for (; ; ) {
14864
- const next = nextFallbackModel(ct, cm, seen, this.configuredFallbackModel(ct));
14865
- if (!next) return cm;
14866
- if (!this.failures.isAbandoned(ct, next)) return next;
14867
- seen.add(modelKey(ct, next));
14868
- }
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;
14869
15246
  }
14870
15247
  async recover(err, state) {
14871
15248
  const { code, recovery } = classifyProviderError(err);
14872
15249
  const message = describeProviderError(err);
14873
- const stay = { kind: "continue", ct: state.ct, cm: state.cm };
15250
+ const stay = { kind: "continue", ct: state.ct, cm: state.cm, entry: state.entry };
14874
15251
  if (code === PROVIDER_ABORTED) return { kind: "give-up", message, code };
14875
- if (recovery === "rotate_credential" && this.rotateCredential())
14876
- return { ...stay, attempt: state.attempt + 1 };
14877
15252
  if (recovery === "retry") {
14878
15253
  await this.backoff(state.attempt);
14879
15254
  return { ...stay, attempt: state.attempt + 1 };
14880
15255
  }
14881
- this.noteFailure(state.ct, state.cm);
15256
+ this.noteFailure(state);
14882
15257
  if (recovery === "abort") return { kind: "give-up", message, code };
14883
- const next = this.escalate(state, { sameProviderFirst: recovery === "switch_model" });
15258
+ const next = this.escalate(state);
14884
15259
  if (next) return next;
14885
15260
  return {
14886
15261
  kind: "give-up",
@@ -14891,36 +15266,40 @@ var ProviderRouter = class {
14891
15266
  code
14892
15267
  };
14893
15268
  }
14894
- escalate(state, opts) {
14895
- if (opts.sameProviderFirst) {
14896
- const next = nextFallbackModel(
14897
- state.ct,
14898
- state.cm,
14899
- state.tried,
14900
- this.configuredFallbackModel(state.ct)
14901
- );
14902
- if (next) {
14903
- this.telemetry.counter(METRIC.MODEL_FALLBACK, 1, {
14904
- "epoch.model.from": modelKey(state.ct, state.cm),
14905
- "epoch.model.to": modelKey(state.ct, next)
14906
- });
14907
- state.tried.add(modelKey(state.ct, next));
14908
- return { kind: "continue", ct: state.ct, cm: next, attempt: 0 };
14909
- }
14910
- }
14911
- const fb = this.fallbackProvider(state.ct, state.tried);
14912
- if (!fb) return null;
14913
- state.tried.add(modelKey(fb.to, fb.toModel));
14914
- 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
+ };
14915
15284
  }
14916
- configuredFallbackModel(ct) {
14917
- return ct === this.config.provider.type ? this.config.fallback?.model : void 0;
15285
+ afterRetries(state) {
15286
+ this.noteFailure(state);
15287
+ return this.escalate(state);
14918
15288
  }
14919
- noteFailure(provider, model) {
14920
- if (!this.failures.recordFailure(provider, model)) return;
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;
15296
+ }
15297
+ noteFailure(state) {
15298
+ if (!this.failures.recordFailure(this.routeOf(state.ct, state.entry))) return;
14921
15299
  this.notices.push(
14922
15300
  t("provider_route.give_up_model", {
14923
- model: modelKey(provider, model),
15301
+ entry: state.entry,
15302
+ model: state.cm,
14924
15303
  count: MODEL_GIVE_UP_THRESHOLD
14925
15304
  })
14926
15305
  );
@@ -14932,30 +15311,50 @@ var ProviderRouter = class {
14932
15311
  async generate(input) {
14933
15312
  const state = this.startPoint(input);
14934
15313
  const requestedModel = state.cm;
14935
- const messages = toModelMessages(input.messages);
15314
+ const messagesFor = messageTranslator(input.messages, this.entryTable);
14936
15315
  const tools = input.tools ? toSdkTools(input.tools) : void 0;
14937
15316
  const output = input.responseFormat ? toOutputSpec(input.responseFormat) : void 0;
14938
- while (state.attempt <= this.config.retry.maxRetries) {
14939
- 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;
14940
15334
  try {
14941
- 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);
14942
15338
  const result = await generateText({
14943
15339
  model,
14944
- messages,
15340
+ messages: messagesFor(ct, entry),
14945
15341
  tools,
14946
15342
  system: input.system,
14947
15343
  stopWhen: isStepCount(input.maxSteps ?? 1),
14948
- ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
15344
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {},
14949
15345
  ...input.signal ? { abortSignal: input.signal } : {},
14950
15346
  output,
14951
- providerOptions: toSdkProviderOptions(input.providerOptions)
15347
+ providerOptions: toSdkProviderOptions(
15348
+ mergeProviderOptions(input.providerOptions, effort?.providerOptions)
15349
+ ),
15350
+ ...effort ? { reasoning: effort.reasoning } : {}
14952
15351
  });
14953
15352
  const toolCalls = normalizeToolCalls(
14954
15353
  result.toolCalls
14955
15354
  );
14956
15355
  const steps = result.steps;
14957
15356
  const reasoningText = steps?.map((s) => s.reasoningText).filter(Boolean).join("\n") || void 0;
14958
- this.failures.recordSuccess(ct, cm);
15357
+ this.failures.recordSuccess(this.routeOf(ct, entry));
14959
15358
  const usage = toTokenUsage(result.usage);
14960
15359
  const warnings = toProviderWarnings(result.warnings);
14961
15360
  const applied = input.responseFormat ? didApplyResponseFormat(result.warnings) : void 0;
@@ -14977,39 +15376,54 @@ var ProviderRouter = class {
14977
15376
  if (plan.kind === "give-up") throw new ProviderError(plan.message, plan.code, false);
14978
15377
  state.ct = plan.ct;
14979
15378
  state.cm = plan.cm;
15379
+ state.entry = plan.entry;
14980
15380
  state.attempt = plan.attempt;
14981
15381
  }
14982
15382
  }
14983
- this.noteFailure(state.ct, state.cm);
14984
- throw new ProviderError(
14985
- t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries }),
14986
- 9999,
14987
- false
14988
- );
14989
15383
  }
14990
15384
  async *stream(input) {
14991
15385
  const state = this.startPoint(input);
14992
15386
  const requestedModel = state.cm;
14993
15387
  let emitted = false;
14994
- const messages = toModelMessages(input.messages);
15388
+ const messagesFor = messageTranslator(input.messages, this.entryTable);
14995
15389
  const tools = input.tools ? toSdkTools(input.tools) : void 0;
14996
15390
  const output = input.responseFormat ? toOutputSpec(input.responseFormat) : void 0;
14997
15391
  let streamPartError;
14998
- while (state.attempt <= this.config.retry.maxRetries) {
14999
- 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;
15000
15409
  streamPartError = void 0;
15001
15410
  try {
15002
- 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);
15003
15414
  const result = streamText({
15004
15415
  model,
15005
- messages,
15416
+ messages: messagesFor(ct, entry),
15006
15417
  tools,
15007
15418
  system: input.system,
15008
15419
  stopWhen: isStepCount(input.maxSteps ?? 1),
15009
- ...input.maxTokens ? { maxOutputTokens: input.maxTokens } : {},
15420
+ ...maxTokens ? { maxOutputTokens: maxTokens } : {},
15010
15421
  ...input.signal ? { abortSignal: input.signal } : {},
15011
15422
  output,
15012
- providerOptions: toSdkProviderOptions(input.providerOptions)
15423
+ providerOptions: toSdkProviderOptions(
15424
+ mergeProviderOptions(input.providerOptions, effort?.providerOptions)
15425
+ ),
15426
+ ...effort ? { reasoning: effort.reasoning } : {}
15013
15427
  });
15014
15428
  for await (const part of result.fullStream) {
15015
15429
  const typed = part;
@@ -15048,7 +15462,7 @@ var ProviderRouter = class {
15048
15462
  const warnings = toProviderWarnings(raw);
15049
15463
  const applied = input.responseFormat ? didApplyResponseFormat(raw) : void 0;
15050
15464
  this.noteResponseFormat(input, applied ?? true, state);
15051
- this.failures.recordSuccess(ct, cm);
15465
+ this.failures.recordSuccess(this.routeOf(ct, entry));
15052
15466
  yield {
15053
15467
  type: "finish",
15054
15468
  ...usage ? { usage } : {},
@@ -15073,14 +15487,15 @@ var ProviderRouter = class {
15073
15487
  }
15074
15488
  state.ct = plan.ct;
15075
15489
  state.cm = plan.cm;
15490
+ state.entry = plan.entry;
15076
15491
  state.attempt = plan.attempt;
15077
15492
  }
15078
15493
  }
15079
- this.noteFailure(state.ct, state.cm);
15080
- yield {
15081
- type: "error",
15082
- error: t("provider_route.retries_exhausted", { count: this.config.retry.maxRetries })
15083
- };
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;
15084
15499
  }
15085
15500
  async backoff(n) {
15086
15501
  await new Promise(
@@ -15097,24 +15512,22 @@ var ProviderRouter = class {
15097
15512
  return this.selection.provider;
15098
15513
  }
15099
15514
  getApiKeyHint() {
15100
- if (this.selection.provider === this.config.provider.type) {
15101
- const c = this.config.provider.credentials[this.credentialIndex];
15102
- return c ? maskApiKey(c.apiKey) : "none";
15103
- }
15104
- const opts = this.available.get(this.selection.provider);
15105
- 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";
15106
15518
  }
15107
15519
  getCapability() {
15108
- 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);
15109
15522
  }
15110
15523
  async probeImageSupport() {
15111
- const { provider, model } = this.selection;
15524
+ const { provider, model, entry } = this.selection;
15112
15525
  const home = this.config.homeDir;
15113
15526
  const cached = readImageProbe(home, provider, model);
15114
15527
  if (cached !== void 0) return cached;
15115
15528
  let lm;
15116
15529
  try {
15117
- lm = await this.getModel(provider, model);
15530
+ lm = await this.getModel(this.routeOf(provider, entry), model);
15118
15531
  } catch {
15119
15532
  return false;
15120
15533
  }
@@ -15135,6 +15548,21 @@ var ProviderRouter = class {
15135
15548
  });
15136
15549
  }
15137
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
+ }
15138
15566
  var STATIC_MODEL_CATALOG = {
15139
15567
  openai: [
15140
15568
  "gpt-4o",
@@ -15195,6 +15623,9 @@ var PROVIDER_DISCOVERY = {
15195
15623
  perplexity: { baseUrl: "https://api.perplexity.ai", auth: "bearer" },
15196
15624
  togetherai: { baseUrl: "https://api.together.xyz/v1", auth: "bearer" }
15197
15625
  };
15626
+ function discoverySpec(provider) {
15627
+ return PROVIDER_DISCOVERY[provider];
15628
+ }
15198
15629
  function cachePath(homeDir, provider) {
15199
15630
  return join(homeDir, "cache", `models_${provider}.json`);
15200
15631
  }
@@ -15223,14 +15654,14 @@ function saveCache2(homeDir, provider, models) {
15223
15654
  }
15224
15655
  }
15225
15656
  function modelListCandidates(baseUrl) {
15226
- const base = baseUrl.trim().replace(/\/+$/, "");
15227
- if (!base) return [];
15228
- const alt = base.endsWith("/v1") ? base.slice(0, -"/v1".length) : `${base}/v1`;
15229
- 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];
15230
15661
  }
15231
- function buildRequest(base, auth, apiKey) {
15662
+ function buildRequest(base2, auth, apiKey) {
15232
15663
  const headers = { Accept: "application/json" };
15233
- let url = `${base}/models`;
15664
+ let url = `${base2}/models`;
15234
15665
  if (auth === "x-api-key") {
15235
15666
  headers["x-api-key"] = apiKey;
15236
15667
  headers["anthropic-version"] = "2023-06-01";
@@ -15253,9 +15684,9 @@ function parseModelList(payload) {
15253
15684
  return [...ids, ...names].filter((v) => typeof v === "string" && v.length > 0).map((id) => id.startsWith("models/") ? id.slice("models/".length) : id);
15254
15685
  }
15255
15686
  async function fetchFromApi(baseUrl, apiKey, auth) {
15256
- for (const base of modelListCandidates(baseUrl)) {
15687
+ for (const base2 of modelListCandidates(baseUrl)) {
15257
15688
  try {
15258
- const { url, headers } = buildRequest(base, auth, apiKey);
15689
+ const { url, headers } = buildRequest(base2, auth, apiKey);
15259
15690
  const resp = await fetch(url, { headers, signal: AbortSignal.timeout(1e4) });
15260
15691
  if (!resp.ok) continue;
15261
15692
  const models = parseModelList(await resp.json());
@@ -15268,8 +15699,8 @@ async function fetchFromApi(baseUrl, apiKey, auth) {
15268
15699
  }
15269
15700
  async function fetchOllamaModels() {
15270
15701
  try {
15271
- const base = process.env.OLLAMA_BASE_URL || "http://localhost:11434";
15272
- 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) });
15273
15704
  if (!resp.ok) return [];
15274
15705
  const data = await resp.json();
15275
15706
  return (data.models ?? []).map((m) => m.name).filter((n) => !!n);
@@ -15285,9 +15716,9 @@ async function discoverModels(homeDir, provider, apiKey, baseUrl) {
15285
15716
  }
15286
15717
  if (apiKey) {
15287
15718
  const spec = PROVIDER_DISCOVERY[provider];
15288
- const base = baseUrl?.trim() || spec?.baseUrl || "";
15289
- if (base) {
15290
- 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");
15291
15722
  if (models) {
15292
15723
  saveCache2(homeDir, provider, models);
15293
15724
  return { models, source: "live" };
@@ -15305,6 +15736,102 @@ function clearModelCache(homeDir, provider) {
15305
15736
  } catch {
15306
15737
  }
15307
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
+ }
15308
15835
  var SETTING_WRITE_LAYERS = ["user", "projectLocal"];
15309
15836
  function isSettingWriteLayer(value) {
15310
15837
  return SETTING_WRITE_LAYERS.includes(value);
@@ -15429,6 +15956,25 @@ function writeSettingValue(req) {
15429
15956
  }
15430
15957
  return req.layer === "user" ? writeUserYaml(req.path, req.key, value) : writeSettingsJson(req.path, req.key, value);
15431
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
+ }
15432
15978
  function writeUserYaml(path, key, value) {
15433
15979
  const text = existsSync(path) ? readFileSync(path, "utf-8") : "";
15434
15980
  const before = asMapping(parseYaml(text));
@@ -18819,7 +19365,7 @@ var GoalService = class {
18819
19365
  }
18820
19366
  };
18821
19367
  // src/goal/provider.ts
18822
- function withGoalPrompt(base, view) {
19368
+ function withGoalPrompt(base2, view) {
18823
19369
  const decorate = (input) => {
18824
19370
  const now = view();
18825
19371
  if (!now) return input;
@@ -18828,9 +19374,9 @@ function withGoalPrompt(base, view) {
18828
19374
 
18829
19375
  ${block}` } : input;
18830
19376
  };
18831
- const wrapped = Object.create(base);
18832
- wrapped.generate = (input) => base.generate(decorate(input));
18833
- 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));
18834
19380
  return wrapped;
18835
19381
  }
18836
19382
  // src/goal/engine.ts
@@ -19339,9 +19885,9 @@ function matchPreauthorization(policy, target) {
19339
19885
  }
19340
19886
  return null;
19341
19887
  }
19342
- function mergeHeadlessPolicy(base, override) {
19888
+ function mergeHeadlessPolicy(base2, override) {
19343
19889
  return {
19344
- ...base,
19890
+ ...base2,
19345
19891
  ...override?.allowTools ? { allowTools: override.allowTools } : {},
19346
19892
  ...override?.allowOperations ? { allowOperations: override.allowOperations } : {}
19347
19893
  };
@@ -21097,14 +21643,14 @@ var TextGuard = class {
21097
21643
  }
21098
21644
  consume(unit) {
21099
21645
  const hits = this.host.scan(this.host.skipCodeBlocks ? this.blankCode(unit) : unit);
21100
- const base = this.consumed;
21646
+ const base2 = this.consumed;
21101
21647
  this.consumed += unit.length;
21102
21648
  if (hits.length === 0) {
21103
21649
  this.out += unit;
21104
21650
  return unit;
21105
21651
  }
21106
21652
  for (const hit of hits) {
21107
- 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 });
21108
21654
  }
21109
21655
  const action = strongestOf(hits);
21110
21656
  if (action === "block") {
@@ -21729,7 +22275,7 @@ function loadLexiconDir(dir, source) {
21729
22275
  }
21730
22276
  const out = { rules: [], exempt: [], issues: [] };
21731
22277
  for (const file of files.sort()) {
21732
- const spec = classify(file);
22278
+ const spec = classify2(file);
21733
22279
  if (!spec) {
21734
22280
  out.issues.push({
21735
22281
  path: file,
@@ -21753,14 +22299,14 @@ function loadLexiconDir(dir, source) {
21753
22299
  }
21754
22300
  return out;
21755
22301
  }
21756
- function classify(file) {
21757
- const base = file.slice(0, -".txt".length);
21758
- if (base === "exempt") return { kind: "exempt" };
21759
- if (base.endsWith(".regex")) {
21760
- 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);
21761
22307
  return CATEGORY_SET.has(category) ? { kind: "regex", category } : null;
21762
22308
  }
21763
- return CATEGORY_SET.has(base) ? { kind: "terms", category: base } : null;
22309
+ return CATEGORY_SET.has(base2) ? { kind: "terms", category: base2 } : null;
21764
22310
  }
21765
22311
  function collect3(content, file, spec, source, out) {
21766
22312
  const lines = content.split("\n");
@@ -22859,8 +23405,8 @@ function copyTree(from, to, label, budget, issues, lang) {
22859
23405
  return left;
22860
23406
  }
22861
23407
  function defaultName(source) {
22862
- const base = basename(source);
22863
- 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;
22864
23410
  }
22865
23411
  function skillSegmentIssue(segment, lang) {
22866
23412
  if (segment.length === 0) return t("skill.import_bad_name_empty", void 0, lang);
@@ -23043,8 +23589,8 @@ function loadOne(mdFile, catPath, category, dirName, spec, skills, issues) {
23043
23589
  const parsed = parseSkillFrontmatter(raw, label);
23044
23590
  issues.push(...parsed.issues);
23045
23591
  const declared = parsed.fm.name?.trim();
23046
- const base = declared ? declared : dirName;
23047
- const name = spec.prefix ? `${spec.prefix}:${base}` : base;
23592
+ const base2 = declared ? declared : dirName;
23593
+ const name = spec.prefix ? `${spec.prefix}:${base2}` : base2;
23048
23594
  const previous = skills.get(name);
23049
23595
  if (previous && previous.scope !== scope) {
23050
23596
  issues.push({
@@ -23201,9 +23747,9 @@ var SkillSystem = class {
23201
23747
  const indexName = declared ? declared : name;
23202
23748
  if (this.skills.has(indexName)) throw new Error(`Skill "${indexName}" already exists`);
23203
23749
  const dir = join(this.skillsDir, category, indexName);
23204
- const base = resolve(this.skillsDir);
23750
+ const base2 = resolve(this.skillsDir);
23205
23751
  const resolved = resolve(dir);
23206
- if (resolved === base || !resolved.startsWith(base + sep)) {
23752
+ if (resolved === base2 || !resolved.startsWith(base2 + sep)) {
23207
23753
  throw new Error(
23208
23754
  `Skill name or category escapes the skills directory: ${category}/${indexName}`
23209
23755
  );
@@ -23252,12 +23798,12 @@ var SkillSystem = class {
23252
23798
  this.invalidateCache();
23253
23799
  }
23254
23800
  resolveInsideSkill(skillPath, relPath) {
23255
- const base = resolve(skillPath);
23801
+ const base2 = resolve(skillPath);
23256
23802
  const resolved = resolve(join(skillPath, relPath));
23257
- if (resolved !== base && !resolved.startsWith(base + sep)) {
23803
+ if (resolved !== base2 && !resolved.startsWith(base2 + sep)) {
23258
23804
  throw new Error("Path traversal denied");
23259
23805
  }
23260
- const realBase = realpathSync(base);
23806
+ const realBase = realpathSync(base2);
23261
23807
  const realTarget = realpathOfDeepestExisting(resolved);
23262
23808
  if (realTarget !== realBase && !realTarget.startsWith(realBase + sep)) {
23263
23809
  throw new Error("Path traversal denied");
@@ -24281,7 +24827,7 @@ var ContextCompressor = class {
24281
24827
  }
24282
24828
  async generateSummary(middleText, instruction, plan, goal) {
24283
24829
  try {
24284
- 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.
24285
24831
 
24286
24832
  ## Previous Summary
24287
24833
  ${this.lastSummary}
@@ -24293,9 +24839,9 @@ ${SUMMARY_TEMPLATE}
24293
24839
 
24294
24840
  ## Conversation
24295
24841
  ${middleText}`;
24296
- const withPlan = plan?.trim() ? `${base}
24842
+ const withPlan = plan?.trim() ? `${base2}
24297
24843
 
24298
- ${planSection(plan.trim())}` : base;
24844
+ ${planSection(plan.trim())}` : base2;
24299
24845
  const withGoal = goal?.trim() ? `${withPlan}
24300
24846
 
24301
24847
  ${goalSection(goal.trim())}` : withPlan;
@@ -24365,10 +24911,10 @@ var SCOPES = ["directory", "directory-tree"];
24365
24911
  function describe14(err) {
24366
24912
  return err instanceof Error ? err.message : String(err);
24367
24913
  }
24368
- function covers(base, target, scope) {
24369
- if (samePath(base, target)) return true;
24914
+ function covers(base2, target, scope) {
24915
+ if (samePath(base2, target)) return true;
24370
24916
  if (scope === "directory") return false;
24371
- const rel = relative(base, target);
24917
+ const rel = relative(base2, target);
24372
24918
  return !rel.startsWith("..") && !isAbsolute(rel);
24373
24919
  }
24374
24920
  var TrustManager = class {
@@ -24384,10 +24930,10 @@ var TrustManager = class {
24384
24930
  let bestLen = -1;
24385
24931
  let best;
24386
24932
  for (const record of this.records) {
24387
- const base = resolve(record.path);
24388
- if (!covers(base, target, record.scope)) continue;
24389
- if (base.length > bestLen || base.length === bestLen && record.level === "untrusted") {
24390
- 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;
24391
24937
  best = record;
24392
24938
  }
24393
24939
  }
@@ -24883,4 +25429,4 @@ function trunc(stdout, stderr) {
24883
25429
  * `--tree` 的命令帮助里,让用户自己知道 `--tree` 意味着什么。
24884
25430
  */
24885
25431
 
24886
- 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 };