@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.
- package/README.md +35 -8
- package/dist/index.d.ts +1501 -1157
- package/dist/index.js +1109 -563
- package/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ToolProvider, EpochTool, EpochPlugin, ToolExposure, EpochSkill, HookType, EpochHook, PluginContext, QuestionRequest, QuestionAnswer, ComplianceCategory, ComplianceAction, ToolArtifactInput, ToolArtifact, TrustLevel, TrustManager as TrustManager$1, TokenUsage, EpochConfig, BudgetBreach, NoticeCode, ProviderType, EpochMessage, JsonSchema, ModelSelection, BackgroundTaskInfo, EpochToolCall, Telemetry, HookManager as HookManager$1, Lang, PermissionManager as PermissionManager$1, ApprovalRequest, ApprovalReply, AgentRoleScope, AgentRoleSource, EpochUserContent, EpochContentPart, AgentRole, PermissionLevel, PlanProposal, ScheduleDefinition, DiagnosticSink, CustomCommandDef, ModelRef, ExpandedCommand, CustomCommandSource, ContextBreakdown, WireMcpConnector, AgentEvent, SelectionContext, SetSelectionResult,
|
|
1
|
+
import { ToolProvider, EpochTool, EpochPlugin, ToolExposure, EpochSkill, HookType, EpochHook, PluginContext, QuestionRequest, QuestionAnswer, ComplianceCategory, ComplianceAction, ToolArtifactInput, ToolArtifact, TrustLevel, TrustManager as TrustManager$1, TokenUsage, EpochConfig, BudgetBreach, NoticeCode, ProviderType, EpochMessage, JsonSchema, ModelSelection, BackgroundTaskInfo, EpochToolCall, Telemetry, HookManager as HookManager$1, Lang, PermissionManager as PermissionManager$1, ApprovalRequest, ApprovalReply, AgentRoleScope, AgentRoleSource, EpochUserContent, EpochContentPart, AgentRole, PermissionLevel, PlanProposal, ScheduleDefinition, DiagnosticSink, CustomCommandDef, ModelRef, ExpandedCommand, CustomCommandSource, ContextBreakdown, WireMcpConnector, AgentEvent, SelectionContext, SetSelectionResult, ModelEntry, ReasoningState, ReasoningChoice, PermissionRuleLists, MoneyDigits, CurrencyDisplay, KeybindingTable, Diagnostic, KeybindingReport, FileOmitReason, SessionOmitReason, EpochToolResult, EpochTurnFacts, LocalizedDetail, ApprovalScope, OperationType, RuleValue, Operation, ApprovalOutcome, PermissionDecision, ScheduleTrigger, ScheduleAllowlist, ScheduleBackendKind, ScheduleRunStatus, ScheduleRun, SchedulePendingApproval, ScheduleDraft, ScheduleWideningKind, WireAgentEvent, WireAgentEnvelope, ScheduleRunnerSpec, HookEvent, LogFn, HookRegistration, HookHandler, HookContext, VerifyResult, EpochFilePart, EpochSessionPart, MentionSet, EpochImagePart, TrustExecGrant, DecidedTrustLevel, TrustScope, TrustGrant, TrustRecord } from '@epoch-agent/protocol';
|
|
2
2
|
export { API_KEY_ENV_VARS, AgentEvent, AgentEventOf, AgentEventType, ApprovalAnswer, ApprovalOutcome, ApprovalReply, ApprovalRequest, ApprovalScope, BudgetBreach, COMPLIANCE_ACTIONS, COMPLIANCE_CATEGORIES, ComplianceAction, ComplianceCategory, DecidedTrustLevel, EpochConfig, EpochHook, EpochPlugin, EpochSkill, EpochTool, FinishReason, HookContext, HookEvent, HookHandler, HookRegistration, HookType, HookManager as IHookManager, PermissionManager as IPermissionManager, TrustManager as ITrustManager, JsonSchema, MAX_ATTACH_BYTES, MAX_ATTACH_FILES, MAX_ATTACH_TOTAL_BYTES, MAX_SESSION_ATTACH_BYTES, MAX_SESSION_REFS, OPERATION_TYPES, Operation, OperationType, PERMISSION_LEVELS, PLAN_OUTCOMES, PROVIDER_INFOS, PROVIDER_TYPES, PermissionDecision, PermissionLevel, PlanApprovalOutcome, PlanProposal, PluginContext, ProviderInfo, ProviderType, TokenUsage, ToolContext, ToolDefinition, ToolExposure, ToolResult, TrustExecGrant, TrustGrant, TrustLevel, TrustRecord, TrustScope, TrustTier, VerifyResult, apiKeyEnvVar, extractAllMentions, extractMentions, extractSessionMentions, getProviderInfo, isOperationType, isPermissionLevel, isPlanOutcome, isProviderType, normalizeApproval } from '@epoch-agent/protocol';
|
|
3
3
|
import { IsolationBackend, SqliteDatabase, ParseIssue, ShellFlavor } from '@epoch-agent/infra';
|
|
4
4
|
export { BubblewrapOptions, DangerMatch, IS_WINDOWS, IsolationBackend, IsolationOptions, LogLevel, SeatbeltOptions, 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';
|
|
@@ -2250,18 +2250,15 @@ interface GenerateInput {
|
|
|
2250
2250
|
*/
|
|
2251
2251
|
signal?: AbortSignal;
|
|
2252
2252
|
/**
|
|
2253
|
-
*
|
|
2253
|
+
* 这一次走「我的模型」里的哪个条目(方案 19 → 2.0.0)。不给 = 当前 selection。
|
|
2254
2254
|
*
|
|
2255
|
-
*
|
|
2256
|
-
*
|
|
2257
|
-
*
|
|
2255
|
+
* 点名了就是「就要这个」:压缩器、起标题这类内部杂活拿 `UtilityModelInfo.selection.entry`
|
|
2256
|
+
* 点名副模型。条目不存在或没凭据时**报错,不静默退回当前那条** —— 静默退回会让
|
|
2257
|
+
* 「我明明配了便宜模型怎么账单没降」变成无从排查的事。
|
|
2258
2258
|
*
|
|
2259
|
-
*
|
|
2260
|
-
* 静默退回会让「我明明配了便宜模型怎么账单没降」变成无从排查的事。
|
|
2259
|
+
* 2.0.0 之前还有 `provider` / `model` 两格(点名一家 + 一个裸模型名),随老写法删了。
|
|
2261
2260
|
*/
|
|
2262
|
-
|
|
2263
|
-
/** 这一次用哪个模型。不给 = 当前 selection(方案 26 之前是 `config.model`) */
|
|
2264
|
-
model?: string;
|
|
2261
|
+
entry?: string;
|
|
2265
2262
|
/**
|
|
2266
2263
|
* 强制结构化输出(方案 59 §2.1)。**provider 无关** —— 底下走 AI SDK 的 `Output`,
|
|
2267
2264
|
* 由它翻成各家的原生机制,**不是**往请求里塞 `response_format`(那是 OpenAI 的
|
|
@@ -2445,7 +2442,7 @@ interface UtilityModelInfo {
|
|
|
2445
2442
|
selection: ModelSelection;
|
|
2446
2443
|
/** 用户显式配过 `models.utility` 吗 */
|
|
2447
2444
|
configured: boolean;
|
|
2448
|
-
/** 配了但用不了的原因(解析不出来 /
|
|
2445
|
+
/** 配了但用不了的原因(解析不出来 / 那一条没凭据)。此时 selection 已退回主模型 */
|
|
2449
2446
|
issue?: string;
|
|
2450
2447
|
}
|
|
2451
2448
|
|
|
@@ -10478,11 +10475,24 @@ declare class ProviderRouter {
|
|
|
10478
10475
|
* 不碰任何 SDK —— SDK 到第一次真正要模型时才 import。
|
|
10479
10476
|
*/
|
|
10480
10477
|
private available;
|
|
10481
|
-
/**
|
|
10478
|
+
/**
|
|
10479
|
+
* 已加载的工厂缓存,key 相同则复用。
|
|
10480
|
+
*
|
|
10481
|
+
* 这两张表的 key 是**路由键**(2026-10-15):「我的模型」里的条目是 `@<id>` ——
|
|
10482
|
+
* 同一家两套端点(官方 + 中转)就是两个 key。判据在 `entry-route.ts` 的文件头。
|
|
10483
|
+
*/
|
|
10482
10484
|
private factories;
|
|
10483
|
-
|
|
10484
|
-
|
|
10485
|
-
|
|
10485
|
+
/**
|
|
10486
|
+
* 「我的模型」条目表。构造时从 config 拿一次;存 / 删之后由 {@link reloadModelEntries}
|
|
10487
|
+
* 整批换掉(开着的会话也要看得见新表)
|
|
10488
|
+
*/
|
|
10489
|
+
private entryTable;
|
|
10490
|
+
/**
|
|
10491
|
+
* 这段会话手动选的推理强度(输入框旁那张「选择强度」卡,2026-10-15)。
|
|
10492
|
+
* `undefined` = 没选过,用条目声明的默认档。**换模型就清掉**:档位是对那一条说的,
|
|
10493
|
+
* 换到一条只认「低 / 高」的模型上还拿着「中」,发出去就是一个它不认的值
|
|
10494
|
+
*/
|
|
10495
|
+
private effort;
|
|
10486
10496
|
/**
|
|
10487
10497
|
* 遥测出口(方案 14)。降级是最值得被观测的一件事 —— 它同时意味着
|
|
10488
10498
|
* 「主 provider 出问题了」和「这次请求换了个模型、单价可能不一样」。
|
|
@@ -10511,7 +10521,7 @@ declare class ProviderRouter {
|
|
|
10511
10521
|
* 分家之后「`reset` 会把你送回哪儿」只有这一格答得上,所以它有了一个读口:
|
|
10512
10522
|
* {@link getConfiguredModel}。
|
|
10513
10523
|
*/
|
|
10514
|
-
private
|
|
10524
|
+
private configSelection;
|
|
10515
10525
|
/** 模型连续失败的节流状态(方案 26 验收 #8)。只在本会话内有效,不落盘 */
|
|
10516
10526
|
private readonly failures;
|
|
10517
10527
|
/**
|
|
@@ -10526,6 +10536,10 @@ declare class ProviderRouter {
|
|
|
10526
10536
|
private readonly notices;
|
|
10527
10537
|
/** 装配层注入遥测。不注入就是零分配空转 */
|
|
10528
10538
|
setTelemetry(telemetry: Telemetry): void;
|
|
10539
|
+
/**
|
|
10540
|
+
* @throws {ProviderError} `PROVIDER_NO_CREDENTIAL`:还没有模型(`model` 没配 / 指向的条目不在),
|
|
10541
|
+
* 或者那一条没有可用的 key。装配层据此进「没有可用模型」那一档(Web 只读、引导去加一条)
|
|
10542
|
+
*/
|
|
10529
10543
|
constructor(config: EpochConfig);
|
|
10530
10544
|
/** 当前生效的选择。回一份拷贝,免得调用方改到内部状态 */
|
|
10531
10545
|
getSelection(): ModelSelection;
|
|
@@ -10540,7 +10554,7 @@ declare class ProviderRouter {
|
|
|
10540
10554
|
*/
|
|
10541
10555
|
getConfiguredModel(): string;
|
|
10542
10556
|
/**
|
|
10543
|
-
*
|
|
10557
|
+
* 换模型:换到「我的模型」里的另一条。
|
|
10544
10558
|
*
|
|
10545
10559
|
* **换模型不清空上下文**——那正是它的意义所在(对着同一段对话换个更强/更便宜
|
|
10546
10560
|
* 的脑子)。代价是新模型必须接得住已经积累的东西,所以有 {@link SelectionContext}
|
|
@@ -10571,6 +10585,28 @@ declare class ProviderRouter {
|
|
|
10571
10585
|
* 比不换更难排查。
|
|
10572
10586
|
*/
|
|
10573
10587
|
private applySelection;
|
|
10588
|
+
/**
|
|
10589
|
+
* 换一张条目表(2026-10-15):设置页存 / 删一条之后,**已经开着的会话**也要看得见。
|
|
10590
|
+
*
|
|
10591
|
+
* 不换的话这段会话一直拿着构造那一刻的表:新加的那条切不过去(`unknown-entry`),改过的
|
|
10592
|
+
* key / 地址 / 模型名不生效,删掉的那条还拿着缓存的工厂照打。runtime 存完对每个活着的
|
|
10593
|
+
* router 调一次(`SessionModels.reloadEntries`)。
|
|
10594
|
+
*
|
|
10595
|
+
* - 条目路由和它们的工厂缓存整批撤掉、重新注册(key 现读,同构造时的 `registerEntries`);
|
|
10596
|
+
* - 选中的、以及「回到配置里那个」那一格落在条目上的,按新表刷新(改了模型名跟着改);
|
|
10597
|
+
* - 选中的那条被删了:回到配置里那个,留一条通知;
|
|
10598
|
+
* - 手选的强度在新表里不成立了(档位撤了 / 不许关了)就清掉,同 `setEffort` 的判据。
|
|
10599
|
+
*/
|
|
10600
|
+
reloadModelEntries(entries: readonly ModelEntry[] | undefined): void;
|
|
10601
|
+
/**
|
|
10602
|
+
* 选择按当前表刷新(改了模型名跟着改);那条**没了或者这会儿用不了**回 `undefined`。
|
|
10603
|
+
*
|
|
10604
|
+
* ⚠️ 「还在表里」不够,还得在 `available` 里(= 这一轮真有凭据可用)。只看表的话,
|
|
10605
|
+
* 选中那条被清掉 key 之后每一发都在 `startPoint` 当场抛 `cannot_use` —— 那一句在
|
|
10606
|
+
* 重试 / 降级两条循环**之外**,于是降级链一次都走不到、用户也没有任何通知,看起来
|
|
10607
|
+
* 就是「这个模型坏了」。两条成因(删了 / 没 key)由调用处按这张表分句。
|
|
10608
|
+
*/
|
|
10609
|
+
private refreshedSelection;
|
|
10574
10610
|
/**
|
|
10575
10611
|
* 取走攒下的通知,队列清空(方案 26 验收 #8)。
|
|
10576
10612
|
*
|
|
@@ -10578,6 +10614,19 @@ declare class ProviderRouter {
|
|
|
10578
10614
|
* 留着的话下一轮会再报一遍同一件旧事。
|
|
10579
10615
|
*/
|
|
10580
10616
|
drainNotices(): string[];
|
|
10617
|
+
/** 此刻这段会话的推理强度,形状见 protocol 的 `ReasoningState` */
|
|
10618
|
+
getEffort(): ReasoningState;
|
|
10619
|
+
/**
|
|
10620
|
+
* 选一档。`null` = 回到条目的默认档。不在这一条声明的档位里(或者它不许关)就不动、回 false ——
|
|
10621
|
+
* 发一个端点不认的值出去,换来的是一次计费过的 400
|
|
10622
|
+
*/
|
|
10623
|
+
setEffort(choice: ReasoningChoice | null): boolean;
|
|
10624
|
+
/**
|
|
10625
|
+
* 这一发请求带不带强度参数。只有**主循环那种不点名的请求**、而且此刻还落在选中的那一条上才带:
|
|
10626
|
+
* 点名的那几种(压缩 / 起标题这类杂活)不该被用户给主对话选的强度拖慢;
|
|
10627
|
+
* 降级落到别处的那一发也不带 —— 那个档位是对原来那一条说的。
|
|
10628
|
+
*/
|
|
10629
|
+
private reasoningFor;
|
|
10581
10630
|
/**
|
|
10582
10631
|
* `models.utility` 解析到哪儿了(方案 26 §2.5)。
|
|
10583
10632
|
*
|
|
@@ -10587,20 +10636,53 @@ declare class ProviderRouter {
|
|
|
10587
10636
|
* 已经不该用了)。没配 utility 的含义是「跟着主模型走」,不是「跟着启动值走」。
|
|
10588
10637
|
*/
|
|
10589
10638
|
getUtilityInfo(): UtilityModelInfo;
|
|
10639
|
+
/**
|
|
10640
|
+
* 注册凭据:条目逐个注册 —— 宿主条目也在其中(它的 baseUrl 是网关地址,见 host-preset)。
|
|
10641
|
+
* 配置里那一条(主模型)注册不上 = 没有可用 key,当场抛 —— 装配层据此进「没有可用模型」那一档。
|
|
10642
|
+
*/
|
|
10590
10643
|
private initFactories;
|
|
10591
|
-
/** 记下凭证;换 key 时同时废掉旧工厂缓存 */
|
|
10592
|
-
private register;
|
|
10593
|
-
private resolveKey;
|
|
10594
10644
|
/**
|
|
10595
|
-
*
|
|
10645
|
+
* 「我的模型」里的条目逐个注册,路由键 `@<id>`(2026-10-15)。
|
|
10596
10646
|
*
|
|
10597
|
-
*
|
|
10598
|
-
*
|
|
10599
|
-
*
|
|
10600
|
-
*
|
|
10601
|
-
*
|
|
10647
|
+
* **key 只有一个来源:条目自己那一把**(`EPOCH_MODEL_KEY_<id>`,设置页存进钥匙串)。
|
|
10648
|
+
* 2026-10-15 之前还会往下退两档 —— 同家时借 `config.provider.apiKey`、不同家时借那家的
|
|
10649
|
+
* 环境变量 key —— 理由是「同一个官方端点,只是换个模型 + 能力声明,不想再粘一遍」。
|
|
10650
|
+
* 那一档取消了:一把 key 是**发给某个端点**的,而「没填地址」只承诺了「那家的官网」,
|
|
10651
|
+
* 主 provider 的地址 / 环境变量却可能指向一个中转站(`DEEPSEEK_API_KEY` 这个名字底下
|
|
10652
|
+
* 完全可以是中转站的 key),借来的那把于是被发到了它不是发给的那个地方。取消之后
|
|
10653
|
+
* 「这条用哪把 key」永远只有条目自己一个答案。
|
|
10654
|
+
*
|
|
10655
|
+
* 没 key 也注册的有三类:自定义端点、ollama、**宿主条目**(`host: true`)。
|
|
10656
|
+
*
|
|
10657
|
+
* - 前两类:本地起的兼容端点多半不要 key;
|
|
10658
|
+
* - ⚠️ 宿主条目是 2026-10-16 用户拍板加的(在那之前它按「别家」处理:没 key 就不注册
|
|
10659
|
+
* —— 内建 provider 的宿主条目于是**构造就抛**,整台机器一个会话都建不出来)。
|
|
10660
|
+
* 判据是原话:「宿主自定义的模型,可能是不需要 apikey 的,可能调用自己的网关时有
|
|
10661
|
+
* 自己的权限判断,所以这个 key 在宿主这里应该是不是必填的」。宿主条目的凭据本来
|
|
10662
|
+
* 就该由宿主那一侧的 fetch 桥按网关 origin 注入(判据在 `entry-route.ts` 的文件头),
|
|
10663
|
+
* 条目自己一把都没有是**正当形态**,不是缺配置。
|
|
10664
|
+
* ⚠️ 真打出去没带鉴权头时,provider 会回 401/403 —— 那是**宿主网关那一侧**要说的话,
|
|
10665
|
+
* 我们这一层不预先拦(预先拦会把「宿主网关自己会鉴权」这种配法误杀)。
|
|
10666
|
+
*
|
|
10667
|
+
* 别家用户条目没 key 就是「没凭据」:不注册,换过去时由 `no-credential` 那一档拦住。
|
|
10668
|
+
*/
|
|
10669
|
+
private registerEntries;
|
|
10670
|
+
/** 路由键,判据在 `entry-route.ts` 的 `routeKey` 上 */
|
|
10671
|
+
private routeOf;
|
|
10672
|
+
/** 元数据表的结论 + 条目的覆盖。所有「这个模型能做什么」的判断都过这一道 */
|
|
10673
|
+
private capabilityFor;
|
|
10674
|
+
/**
|
|
10675
|
+
* `config.model` 解析成一次选择。
|
|
10676
|
+
*
|
|
10677
|
+
* 没配、或者那一条不在「我的模型」里:**抛 `no_model`**,不造一个模型出来。2.0.0 之前这里退到
|
|
10678
|
+
* 主 provider 的默认模型(`gpt-4o-mini` 那一套),随老写法删了 —— 今天「还没有模型」是一个
|
|
10679
|
+
* 正常状态,装配层接住它、界面引导用户去加一条。
|
|
10602
10680
|
*/
|
|
10603
|
-
private
|
|
10681
|
+
private resolveConfigured;
|
|
10682
|
+
/** 用户写的引用 → 一次选择;条目不存在时直接给出拒绝结果(`ok` 字段在就是结果) */
|
|
10683
|
+
private resolveRef;
|
|
10684
|
+
/** `models.utility` 写的是 `@<id>` 时的那一支,形状同 {@link getUtilityInfo} */
|
|
10685
|
+
private entryUtilityInfo;
|
|
10604
10686
|
/**
|
|
10605
10687
|
* 取模型实例。第一次用到某个 provider 时才 import 它的 SDK。
|
|
10606
10688
|
*
|
|
@@ -10609,22 +10691,22 @@ declare class ProviderRouter {
|
|
|
10609
10691
|
* 装包这件事重试一万次也不会自己好。
|
|
10610
10692
|
*/
|
|
10611
10693
|
private getModel;
|
|
10612
|
-
private rotateCredential;
|
|
10613
|
-
private fallbackProvider;
|
|
10614
10694
|
/**
|
|
10615
|
-
*
|
|
10695
|
+
* 这次请求从哪一条起步(方案 19 → 2.0.0):点名了条目(压缩器 / 内部杂活)就是它,没点名就是当前选择。
|
|
10616
10696
|
*
|
|
10617
|
-
*
|
|
10618
|
-
* `
|
|
10697
|
+
* 点名的那一条用不了(不在表里 / 没 key)**抛,不静默退回当前那条** —— 判据在
|
|
10698
|
+
* `GenerateInput.entry` 上。没点名时过一道节流:当前那条被放弃过就从降级那一条起步
|
|
10699
|
+
* ({@link avoidAbandoned})。
|
|
10619
10700
|
*/
|
|
10620
10701
|
private startPoint;
|
|
10621
10702
|
/**
|
|
10622
|
-
* 起点绕开已经放弃的模型(方案 26 验收 #8
|
|
10703
|
+
* 起点绕开已经放弃的模型(方案 26 验收 #8):当前那条被放弃过、而降级那一条(`fallback.model`)
|
|
10704
|
+
* 还没有,就从降级那一条起步。
|
|
10623
10705
|
*
|
|
10624
10706
|
* 这就是节流真正省下来的东西:不这么做的话,每一轮都要先拿一个已知挂着的
|
|
10625
10707
|
* 模型烧一次请求、等一次超时,然后才降级 —— 而结局每轮都一样。
|
|
10626
10708
|
*
|
|
10627
|
-
*
|
|
10709
|
+
* 降级那一条也被放弃过(或者没配)时**照旧打当前那条**:那时候降到哪儿都一样坏,
|
|
10628
10710
|
* 不如让用户看到他选的那个模型报的真实错误。
|
|
10629
10711
|
*/
|
|
10630
10712
|
private avoidAbandoned;
|
|
@@ -10638,24 +10720,45 @@ declare class ProviderRouter {
|
|
|
10638
10720
|
*/
|
|
10639
10721
|
private recover;
|
|
10640
10722
|
/**
|
|
10641
|
-
*
|
|
10723
|
+
* 沿降级链走一步:降到 `fallback.model` 那一条(方案 26 §2.4 → 2.0.0)。
|
|
10642
10724
|
*
|
|
10643
|
-
*
|
|
10644
|
-
*
|
|
10725
|
+
* 2.0.0 之前降级链有三级(`@<id>` 条目 → 同家内置降级表 → `fallback.providers` 换一家用那家的
|
|
10726
|
+
* 默认模型),后两级都是老写法的产物,随它删了:内置表里是各家**官方端点**上的模型名,拿去打
|
|
10727
|
+
* 一个自建端点必然 404;换一家用「那家的默认模型」等于替用户选了一个他没确认过的模型。
|
|
10645
10728
|
*
|
|
10646
|
-
* `attempt`
|
|
10647
|
-
*
|
|
10729
|
+
* `attempt` 归零:新落点值得一份完整的重试预算。不会转圈是因为 `tried` 在涨。
|
|
10730
|
+
*
|
|
10731
|
+
* 两个调用点:`recover()` 上那些**非重试类**的失败,以及 {@link afterRetries}
|
|
10732
|
+
* —— 而 `afterRetries` 正是「重试类失败也终于降级」的那一处(2026-10-16)。
|
|
10648
10733
|
*/
|
|
10649
10734
|
private escalate;
|
|
10650
10735
|
/**
|
|
10651
|
-
* `
|
|
10736
|
+
* 重试预算在一个落点上跑完之后(2026-10-16):记一笔,沿降级链走一步;没下一条可走时回 `null`。
|
|
10737
|
+
*
|
|
10738
|
+
* ## 为什么预算耗尽也要走降级链
|
|
10652
10739
|
*
|
|
10653
|
-
*
|
|
10654
|
-
*
|
|
10655
|
-
*
|
|
10656
|
-
*
|
|
10740
|
+
* `recover()` 对 `recovery: 'retry'` 只有「留在原地再来一次」一个动作,于是
|
|
10741
|
+
* 「预算耗尽」这一步从来不在它里面发生,而是落在两个循环之外 —— 那儿原来只有
|
|
10742
|
+
* 「记一笔 + 放弃」。后果是降级在**最常见的那一族失败上整个失效**:主条目的端点
|
|
10743
|
+
* 挂了(恒 503 / 恒超时 / 一直限流),整轮把预算烧在同一个落点上,然后直接报
|
|
10744
|
+
* 「重试 N 次后仍然失败」—— 配好的 `fallback.model` 一次都没被打过,而设置页上
|
|
10745
|
+
* 那句「主模型连续出错时换到它」在这种现场里是假的。
|
|
10746
|
+
*
|
|
10747
|
+
* 判据是「**预算耗尽 = 这个落点在这一轮里没什么可试的了**」,和 404 / 401 / 402
|
|
10748
|
+
* 那几档是同一件事,所以出路也走同一条。不会转圈:`escalate()` 把走过的落点记进
|
|
10749
|
+
* `tried`,候选只有 `fallback.model` 那一条,降级那一条再耗尽时这里回 `null`,
|
|
10750
|
+
* 由调用方按各自的方式说「重试 N 次后仍然失败」。
|
|
10751
|
+
*
|
|
10752
|
+
* ⚠️ **记账不挪进 `escalate()`**:跨轮节流的计数单位是「一轮一次」,而两个循环里
|
|
10753
|
+
* 它是那一轮唯一的失败出口。主条目耗尽了记主条目(否则一个稳定 429 的模型永远
|
|
10754
|
+
* 攒不够三次),落到降级条目上再耗尽是另一笔 —— 两次都该记。
|
|
10755
|
+
*/
|
|
10756
|
+
private afterRetries;
|
|
10757
|
+
/**
|
|
10758
|
+
* `fallback.model`(`@<id>`)指向的那一条,**不限同一家**:条目自带端点和 key,
|
|
10759
|
+
* 「主模型挂了换我自建的那个」正是它的用途。就是当前那条、已经试过、或者没 key 时回 `undefined`
|
|
10657
10760
|
*/
|
|
10658
|
-
private
|
|
10761
|
+
private fallbackEntry;
|
|
10659
10762
|
/**
|
|
10660
10763
|
* 记一次模型失败,跨过阈值时留一条通知(方案 26 验收 #8)。
|
|
10661
10764
|
*
|
|
@@ -10680,6 +10783,14 @@ declare class ProviderRouter {
|
|
|
10680
10783
|
generate(input: GenerateInput): Promise<GenerateOutput>;
|
|
10681
10784
|
/** 流式生成,带自动重试和降级 */
|
|
10682
10785
|
stream(input: GenerateInput): AsyncGenerator<ProviderStreamChunk>;
|
|
10786
|
+
/**
|
|
10787
|
+
* 这一次的输出上限:条目声明了 `maxOutputTokens` 时取它和调用方给的那个**较小的**。
|
|
10788
|
+
*
|
|
10789
|
+
* 较小而不是「条目优先」:调用方给得小是有原因的(起标题只要几十个 token),条目那一格
|
|
10790
|
+
* 说的是「这个模型最多能出多少」—— 两个都是上限,同时成立的就是较小的那个。
|
|
10791
|
+
* 调用方没给时用条目的,那是用户在「输出」那一格明说要的。
|
|
10792
|
+
*/
|
|
10793
|
+
private maxTokensFor;
|
|
10683
10794
|
private backoff;
|
|
10684
10795
|
getModelId(): string;
|
|
10685
10796
|
getProviderType(): ProviderType;
|
|
@@ -10725,1332 +10836,1536 @@ declare class ProviderRouter {
|
|
|
10725
10836
|
}
|
|
10726
10837
|
|
|
10727
10838
|
/**
|
|
10728
|
-
*
|
|
10729
|
-
*
|
|
10730
|
-
* ## 为什么需要它:查表这条路有一个它永远答不出的问题
|
|
10731
|
-
*
|
|
10732
|
-
* `model-metadata.ts` 的三层(静态表 → OpenRouter 缓存 → 默认值)全都是
|
|
10733
|
-
* **别人对这个模型的描述**。上游改一次名字,两层同时失效,第三层的默认值是
|
|
10734
|
-
* `supportsImages: false` —— 于是一个认图的模型在我们眼里瞎掉,而且是**静默**
|
|
10735
|
-
* 瞎掉:用户得到的是「当前模型不支持图片输入」,一句可核对的理由都没有。
|
|
10736
|
-
*
|
|
10737
|
-
* 2026-09-11 实测的现场:DeepSeek 把 `deepseek-v4-flash-vision-exp` 改名成
|
|
10738
|
-
* `deepseek-flash`,静态表 miss、OpenRouter 那份 872 条里**根本没有这个裸名**,
|
|
10739
|
-
* 于是 `epoch -i 图.png` 被我们自己拦死。而同一张图直接发给它,它答得一字不差。
|
|
10740
|
-
*
|
|
10741
|
-
* ## 判据是 **input token 涨没涨**,而这一格换过两次,两次都是被实测逼的
|
|
10742
|
-
*
|
|
10743
|
-
* ### 第一版:靠 warning —— 当天就否掉了
|
|
10744
|
-
*
|
|
10745
|
-
* 最省事的做法是不确定就照发,SDK 报了 warning(`droppedUserContentParts`)
|
|
10746
|
-
* 就说明它不认。**走不通**,同一天的 A/B:
|
|
10747
|
-
*
|
|
10748
|
-
* | 模型 | 出站 body | warnings | 回答 | input token |
|
|
10749
|
-
* | ----------------- | --------- | -------- | ----------- | ----------- |
|
|
10750
|
-
* | `deepseek-flash` | 带图 | `[]` | `"Red"` | 225 |
|
|
10751
|
-
* | `deepseek-v4-pro` | 带图 | `[]` | `"Unknown"` | 99 |
|
|
10839
|
+
* 「我的模型」条目在 router 里怎么落地(2026-10-15)。
|
|
10752
10840
|
*
|
|
10753
|
-
*
|
|
10754
|
-
* 两侧都认为这次请求很正常。同理「不报错就算认图」「答了话就算认图」也都被这一行
|
|
10755
|
-
* 否掉 —— 它不报错,它也答了话。
|
|
10841
|
+
* ## 路由键
|
|
10756
10842
|
*
|
|
10757
|
-
*
|
|
10843
|
+
* router 的凭据表和工厂缓存按**路由键**存:条目的是 `@<id>`,同一家两套端点(官方 DeepSeek +
|
|
10844
|
+
* 自建中转的 DeepSeek)就是两个键。2.0.0 之前还有一档「老写法的路由键就是 provider 名」,
|
|
10845
|
+
* 随老写法删了 —— 今天只剩钉住网关时那一条 `openai-compatible`(见下)。
|
|
10846
|
+
* 其余按 provider 记的东西(遥测、tool 结果收不收图)照旧按 provider,
|
|
10847
|
+
* 因为它们说的是「这家的协议」,不是「这一套端点」。
|
|
10758
10848
|
*
|
|
10759
|
-
*
|
|
10760
|
-
* 同一张 132 字节的红方块、同一个型号(`deepseek-v4-flash-vision-exp`)、
|
|
10761
|
-
* **逐字节相同的出站 body**(钩 `fetch` 抓的,四次都是 `bodyLen=50948`),
|
|
10762
|
-
* 连着跑四次的回答是:
|
|
10849
|
+
* ## ⚠️ 宿主钉住端点时,**只有宿主条目**走网关(2026-10-15 收窄)
|
|
10763
10850
|
*
|
|
10764
|
-
*
|
|
10765
|
-
*
|
|
10766
|
-
*
|
|
10767
|
-
*
|
|
10768
|
-
*
|
|
10769
|
-
* | 4 | `Blue` |
|
|
10851
|
+
* 原来的契约是「`config.gateway` 时所有条目一律打网关」,用户拍了收窄:
|
|
10852
|
+
* 钉住钉的是**宿主自己那一组**(`ModelEntry.host`,`hostPreset.models` 物化来的),
|
|
10853
|
+
* 用户添加的条目照常用自己的 provider / 地址 / key 直连自己的端点 —— 不然用户在星云里
|
|
10854
|
+
* 加不了别的提供商。宿主条目的地址由**宿主自己写在条目里**(`hostPreset.models` 每条自带
|
|
10855
|
+
* baseUrl,原样物化、不强制),所以**不需要任何路由特判**:条目永远按自己的字段走。
|
|
10770
10856
|
*
|
|
10771
|
-
*
|
|
10772
|
-
*
|
|
10857
|
+
* 安全论证(为什么可以收窄):
|
|
10858
|
+
* - 宿主条目的凭据是宿主的 fetch 桥**按网关 origin**注入的,用户条目的请求不会拿到它;
|
|
10859
|
+
* - 用户条目只用自己存的那把 key(`EPOCH_MODEL_KEY_<id>`,只发往用户自己填的地址);
|
|
10860
|
+
* - 宿主条目没有特权:它只是被界面锁住的一条普通条目,用户手改 models.json 也
|
|
10861
|
+
* 得不到任何它不该有的凭据。
|
|
10862
|
+
* 守门用例在 `__tests__/entry-route-pinned.test.ts`(宿主条目出站打网关、用户条目打自己的地址)。
|
|
10863
|
+
*/
|
|
10864
|
+
|
|
10865
|
+
/**
|
|
10866
|
+
* 一个模型引用**此刻**能做什么:`@<id>` 先解析成条目,再在元数据表上叠条目的覆盖。
|
|
10773
10867
|
*
|
|
10774
|
-
*
|
|
10868
|
+
* 给 router 之外的那几处用 —— 启动时的上下文窗口快照(runtime 的 `resolveModelCapability`)、
|
|
10869
|
+
* TUI 的状态栏和贴图闸。它们原来直接 `getCapability(config.model)`,而 `config.model` 是
|
|
10870
|
+
* `@xxx` 时那一句查到的是兜底的默认值:窗口 200k、不认图 —— 一个声明过 32k 的条目会
|
|
10871
|
+
* 一路聊到撞窗口。
|
|
10775
10872
|
*
|
|
10776
|
-
*
|
|
10777
|
-
*
|
|
10778
|
-
|
|
10873
|
+
* @param ref 缺省是 `config.model`
|
|
10874
|
+
* @returns 引用缺席 / 不是 `@<id>` / 那一条不在表里时 `undefined`(还没有模型)
|
|
10875
|
+
*/
|
|
10876
|
+
declare function capabilityOfRef(config: Pick<EpochConfig, 'model' | 'homeDir' | 'modelEntries'>, ref?: string | undefined): ModelCapability | undefined;
|
|
10877
|
+
|
|
10878
|
+
/**
|
|
10879
|
+
* 「我的模型」弹窗里那两个问句:**这个端点有哪些模型**、**这套配置连不连得上**(2026-10-15)。
|
|
10779
10880
|
*
|
|
10780
|
-
*
|
|
10881
|
+
* ## 为什么不复用 `discoverModels()`
|
|
10781
10882
|
*
|
|
10782
|
-
*
|
|
10783
|
-
*
|
|
10784
|
-
*
|
|
10883
|
+
* 它是给「已经配好的那一家」用的:探到的结果按 **provider** 落盘缓存 24 小时
|
|
10884
|
+
* (`cache/models_<provider>.json`)。弹窗里探的是**还没保存的草稿** —— 用户可能正在
|
|
10885
|
+
* 试一个填错的中转站地址。走它的话,一次草稿探测就会把「DeepSeek 官方有哪些模型」那份
|
|
10886
|
+
* 缓存换成中转站上的那份,而那份缓存同时喂着输入框旁边的模型菜单。所以这里**一个字节
|
|
10887
|
+
* 都不落盘**,每次现问。
|
|
10785
10888
|
*
|
|
10786
|
-
*
|
|
10787
|
-
* 不在我们的责任边界内。上面那四次 `White` / `Magenta` / `Blue` 在新判据下
|
|
10788
|
-
* 全部算「认图」—— 而它们确实都认图,这正是想要的。
|
|
10889
|
+
* ## 测试连接分两步,第二步才花钱
|
|
10789
10890
|
*
|
|
10790
|
-
*
|
|
10791
|
-
*
|
|
10792
|
-
*
|
|
10891
|
+
* 1. `GET <地址>/models`。不花钱,而且 401 / 403 在这一步就能说清楚是 key 的事;
|
|
10892
|
+
* 2. 列表接口不存在(404、或者「自定义协议」那种只有一个地址的端点)时,才真发一次
|
|
10893
|
+
* `maxOutputTokens: 1` 的请求 —— 那是唯一能回答「这个模型名在这个端点上调不调得通」
|
|
10894
|
+
* 的办法。代价是一次请求的钱,用户按的就是「测试」这个按钮。
|
|
10793
10895
|
*
|
|
10794
|
-
* ##
|
|
10896
|
+
* ## 报错原文里不许带 key
|
|
10795
10897
|
*
|
|
10796
|
-
*
|
|
10797
|
-
*
|
|
10798
|
-
* 处置没变:这一次按「不认图」走,但**一个字都不写缓存** —— 写进去会把一个
|
|
10799
|
-
* 认图的模型钉死(`true` 的缓存是 30 天)。
|
|
10898
|
+
* `detail` 会过网线进浏览器。有的中转站会把收到的 key 回显在报错里,所以出门前把
|
|
10899
|
+
* key 原文替换成脱敏串({@link redact})。
|
|
10800
10900
|
*/
|
|
10801
10901
|
|
|
10902
|
+
/** 测试连接的结论。码是真源,措辞在浏览器的 catalog 里(同 `SelectionRejection`) */
|
|
10903
|
+
type EntryProbeCode =
|
|
10904
|
+
/** 连上了 */
|
|
10905
|
+
'ok'
|
|
10906
|
+
/** 401 / 403:key 不对或没权限 */
|
|
10907
|
+
| 'auth'
|
|
10908
|
+
/** 模型名在这个端点上不存在(404 / model not found) */
|
|
10909
|
+
| 'model-not-found'
|
|
10910
|
+
/** 连不上:DNS / 拒绝连接 / 超时 / 证书 */
|
|
10911
|
+
| 'network'
|
|
10912
|
+
/** 别的错误。`detail` 里是端点的原话 */
|
|
10913
|
+
| 'error'
|
|
10914
|
+
/**
|
|
10915
|
+
* 还没选模型、而这个端点又列不出模型清单(没有列表接口 / 列表接口不通但不是 key 的事)——
|
|
10916
|
+
* 只剩「拿一个具体模型真发一次」这条路能验证,得先选模型。**一发都没有失败**,不是报错
|
|
10917
|
+
*/
|
|
10918
|
+
| 'need-model'
|
|
10919
|
+
/**
|
|
10920
|
+
* 填的东西本地就没过校验(地址格式不对之类),**一发都没出去**。`detail` 是校验的原话。
|
|
10921
|
+
* ⚠️ 2026-10-15 之前这一档报的是 `error`,界面因此说成「接口回了一个错误」—— 那是假话
|
|
10922
|
+
*/
|
|
10923
|
+
| 'invalid';
|
|
10924
|
+
interface EntryProbeResult {
|
|
10925
|
+
code: EntryProbeCode;
|
|
10926
|
+
/** 顺带探到的模型列表(第一步成功时才有,可能为空) */
|
|
10927
|
+
models: string[];
|
|
10928
|
+
/** 端点回的 HTTP 状态码(有的话) */
|
|
10929
|
+
httpStatus?: number;
|
|
10930
|
+
/** 从发出到拿到结论用了多久 */
|
|
10931
|
+
latencyMs: number;
|
|
10932
|
+
/** 端点的原话(已脱敏)。只在不是 `ok` 时给 */
|
|
10933
|
+
detail?: string;
|
|
10934
|
+
}
|
|
10802
10935
|
/**
|
|
10803
|
-
*
|
|
10804
|
-
*
|
|
10805
|
-
* 是第二版那个 3/4 误判率产出的 —— 继续信它等于把一次已经作废的实验的结果
|
|
10806
|
-
* 当成这次的。整份丢掉,重新探。
|
|
10936
|
+
* 这个(可能还没保存的)条目的端点上有哪些模型。**不落缓存**,判据见文件头。
|
|
10937
|
+
* 探不到就是空数组 —— 模型名那一格本来就允许手敲。
|
|
10807
10938
|
*/
|
|
10808
|
-
declare
|
|
10939
|
+
declare function listEntryModels(entry: ModelEntry, apiKey: string | undefined): Promise<string[]>;
|
|
10940
|
+
/** 测试连接。**从不抛**:任何失败都落到一个码上 */
|
|
10941
|
+
declare function testModelEntry(entry: ModelEntry, apiKey: string | undefined): Promise<EntryProbeResult>;
|
|
10809
10942
|
|
|
10810
10943
|
/**
|
|
10811
|
-
*
|
|
10944
|
+
* 模型发现 — Live API → disk cache → static catalog 三层回退 [Hermes models.py]。
|
|
10812
10945
|
*
|
|
10813
|
-
*
|
|
10814
|
-
*
|
|
10815
|
-
*
|
|
10816
|
-
*
|
|
10946
|
+
* 改造前只有 6 个 provider 有 base URL,且 URL 拼接只对「用户传进来的」那份做
|
|
10947
|
+
* `/v1` 归一化,内置那份不做——于是 anthropic / google / xai / perplexity 永远
|
|
10948
|
+
* 走不到 live 列表,而用户填 `https://x.com/v1` 会被拼成 `https://x.com/models`。
|
|
10949
|
+
* 现在 base URL 全部与 `@ai-sdk/*` 各 provider 的默认 chat baseURL 对齐(同一
|
|
10950
|
+
* 个域名下发现和调用才可能一致),`/v1` 有无由候选重试决定而不是靠猜。
|
|
10817
10951
|
*/
|
|
10818
|
-
|
|
10819
|
-
|
|
10820
|
-
|
|
10821
|
-
readonly code: number;
|
|
10822
|
-
readonly recoverable: boolean;
|
|
10823
|
-
readonly recovery: RecoveryAction;
|
|
10824
|
-
constructor(message: string, code: number, recoverable: boolean, recovery?: RecoveryAction);
|
|
10952
|
+
interface DiscoverResult {
|
|
10953
|
+
models: string[];
|
|
10954
|
+
source: 'live' | 'cache' | 'static';
|
|
10825
10955
|
}
|
|
10826
10956
|
/**
|
|
10827
|
-
*
|
|
10828
|
-
*
|
|
10829
|
-
*
|
|
10830
|
-
*
|
|
10831
|
-
*
|
|
10832
|
-
*
|
|
10957
|
+
* 模型列表接口的认证方式。三种都不是我们编的:
|
|
10958
|
+
* - `bearer`:OpenAI 及一众兼容实现
|
|
10959
|
+
* - `x-api-key`:Anthropic 原生 API,另外还要 `anthropic-version`
|
|
10960
|
+
* (见 hermes-agent/plugins/model-providers/anthropic/__init__.py 的 fetch_models)
|
|
10961
|
+
* - `query`:Google Generative Language API 把 key 放 query string
|
|
10962
|
+
* (见 hermes-agent/hermes_cli/web_server.py 的 GEMINI_API_KEY 项)
|
|
10833
10963
|
*/
|
|
10834
|
-
|
|
10964
|
+
type AuthStyle = 'bearer' | 'x-api-key' | 'query';
|
|
10965
|
+
interface ProviderDiscovery {
|
|
10966
|
+
/** 模型列表接口的 base URL */
|
|
10967
|
+
baseUrl: string;
|
|
10968
|
+
auth: AuthStyle;
|
|
10969
|
+
}
|
|
10835
10970
|
/**
|
|
10836
|
-
*
|
|
10837
|
-
*
|
|
10838
|
-
* 顺序很关键,而且**语义模式必须排在裸数字之前**:
|
|
10839
|
-
* 原来 `/500|502|503/` 排在 context-length 检查前面,任何含 "500" 的消息
|
|
10840
|
-
* ("maximum context length is 128500 tokens"、"reduce to 500 tokens")
|
|
10841
|
-
* 都会被当成服务器错误去重试——而 context 超限重试一万次也不会好。
|
|
10842
|
-
*
|
|
10843
|
-
* 裸状态码只在错误对象本身带 status 时才可靠,所以优先读 status,
|
|
10844
|
-
* 再退化到从消息里找**带边界**的状态码。
|
|
10971
|
+
* 这一家的模型列表接口在哪儿、怎么鉴权(给「我的模型」的探测用,2026-10-15)。
|
|
10972
|
+
* 不在表里的(`openai-compatible` / `ollama` / `cohere` …)回 `undefined`。
|
|
10845
10973
|
*/
|
|
10846
|
-
declare function
|
|
10847
|
-
code: number;
|
|
10848
|
-
recovery: RecoveryAction;
|
|
10849
|
-
};
|
|
10850
|
-
|
|
10974
|
+
declare function discoverySpec(provider: string): ProviderDiscovery | undefined;
|
|
10851
10975
|
/**
|
|
10852
|
-
*
|
|
10853
|
-
*
|
|
10854
|
-
* 单独一个文件而不是塞进 `router.ts`:
|
|
10976
|
+
* `<base>/models` 的候选列表:原样一个,再一个把结尾 `/v1` 反转过来的。
|
|
10855
10977
|
*
|
|
10856
|
-
*
|
|
10857
|
-
*
|
|
10858
|
-
*
|
|
10859
|
-
*
|
|
10978
|
+
* provider 到底是 `https://api.deepseek.com/models` 还是
|
|
10979
|
+
* `https://api.deepseek.com/v1/models`、用户填的 base 带不带 `/v1`,猜不准也
|
|
10980
|
+
* 没必要猜——第一个不通就试第二个。这个「toggle 而不是 strip」的做法抄的是
|
|
10981
|
+
* hermes-agent/hermes_cli/models.py 的 `alternate_base`。
|
|
10982
|
+
*/
|
|
10983
|
+
declare function modelListCandidates(baseUrl: string): string[];
|
|
10984
|
+
/**
|
|
10985
|
+
* 从响应里取模型 id。两种形状都要认:
|
|
10986
|
+
* - OpenAI / Anthropic:`{ data: [{ id }] }`
|
|
10987
|
+
* - Google 原生 ListModels:`{ models: [{ name: "models/gemini-2.5-pro" }] }`
|
|
10988
|
+
* —— `models/` 前缀要摘掉,配置和用户输入里写的都是裸名
|
|
10989
|
+
* (hermes models.py:5148 踩过这个坑,注释里挂着 gemini-cli #12532)
|
|
10990
|
+
*/
|
|
10991
|
+
declare function parseModelList(payload: unknown): string[];
|
|
10992
|
+
/**
|
|
10993
|
+
* 发现 provider 可用模型 [Hermes discoverModels]。
|
|
10860
10994
|
*
|
|
10861
|
-
*
|
|
10862
|
-
*
|
|
10863
|
-
*
|
|
10995
|
+
* 1. 调 API 拉取 live 模型列表 → 缓存
|
|
10996
|
+
* 2. 失败 → 读磁盘缓存
|
|
10997
|
+
* 3. 空 → 回退到静态目录
|
|
10864
10998
|
*/
|
|
10999
|
+
declare function discoverModels(homeDir: string, provider: string, apiKey?: string, baseUrl?: string): Promise<DiscoverResult>;
|
|
11000
|
+
/** 清除 provider 的模型缓存 */
|
|
11001
|
+
declare function clearModelCache(homeDir: string, provider: string): void;
|
|
10865
11002
|
|
|
10866
|
-
/** 判定要用到的外部事实,由 router 注入 */
|
|
10867
|
-
interface SelectionDeps {
|
|
10868
|
-
/** 这个 provider 有没有可用凭据 */
|
|
10869
|
-
hasCredential: (provider: ProviderType) => boolean;
|
|
10870
|
-
/** 这个模型的能力(窗口 / 视觉 / 工具) */
|
|
10871
|
-
capability: (model: string) => ModelCapability;
|
|
10872
|
-
}
|
|
10873
11003
|
/**
|
|
10874
|
-
*
|
|
10875
|
-
*
|
|
10876
|
-
* 两条拒绝都是「换过去下一轮必然失败」,**在这里拦住比让 provider 报 400 好**:
|
|
10877
|
-
* 400 是已经计费的,而且错误消息里不会写「因为你刚才换了模型」。
|
|
11004
|
+
* 设置文件的形状与读取 —— 项目级 / 项目本地 / `--settings` / 企业托管**共用这一份**。
|
|
10878
11005
|
*
|
|
10879
|
-
*
|
|
10880
|
-
*
|
|
11006
|
+
* 从 [sources.ts](./sources.ts) 拆出来的原因很具体:企业托管(第 ∞ 层,§2.6)
|
|
11007
|
+
* 的文件形状是「同样的键 + 三个元开关」,它要复用这里的 schema 和读取逻辑,
|
|
11008
|
+
* 而 `sources.ts` 又要消费托管层的解析结果 —— 两个文件互相 import 就成了环。
|
|
11009
|
+
* 把**共用的下层**摘出来是拆环最省事的一刀。
|
|
10881
11010
|
*/
|
|
10882
|
-
declare function evaluateSelection(want: ModelSelection, deps: SelectionDeps, ctx?: SelectionContext): SetSelectionResult;
|
|
10883
11011
|
|
|
10884
11012
|
/**
|
|
10885
|
-
*
|
|
10886
|
-
*
|
|
10887
|
-
* 拆出来而不是继续堆在 `router.ts` 里,有两个理由:
|
|
11013
|
+
* 设置文件(`.epoch/settings.json` / `--settings`)的形状。
|
|
10888
11014
|
*
|
|
10889
|
-
*
|
|
10890
|
-
*
|
|
10891
|
-
*
|
|
11015
|
+
* 字段从 `EpochConfigSchema` **摘**出来而不是另写一份:值域约束(`maxTurns`
|
|
11016
|
+
* 是正整数、`permissions` 三张列表都是非空字符串)只能有一处定义,
|
|
11017
|
+
* 抄第二份迟早对不上。
|
|
10892
11018
|
*
|
|
10893
|
-
*
|
|
11019
|
+
* ## `.unwrap()` 不是花活,`.partial()` 在这儿是错的
|
|
10894
11020
|
*
|
|
10895
|
-
*
|
|
10896
|
-
*
|
|
11021
|
+
* `EpochConfigSchema` 里 `model` / `maxTurns` 都带 `.default()`,而 zod v4 的
|
|
11022
|
+
* `.partial()` 只是往外面套一层 `ZodOptional`,**里面的默认值照样兑现**:
|
|
11023
|
+
* `pick({model:true}).partial().parse({})` 得到的是 `{model:'gpt-4o-mini'}`,
|
|
11024
|
+
* 不是 `{}`。合并语义是「给了才覆盖」,于是一份只写了 `permissions` 的
|
|
11025
|
+
* `settings.json` 会顺手把用户配的 model 打回内置默认值 —— 而且诊断里
|
|
11026
|
+
* 一个字都不会说。所以必须先 `.unwrap()` 摘掉 default 再 `.optional()`。
|
|
10897
11027
|
*
|
|
10898
|
-
*
|
|
10899
|
-
* 这三样同时变,而用户多半只是想让这一轮跑完。
|
|
11028
|
+
* 用例里钉了「`parse({})` 必须深等于 `{}`」,它红了就说明这层保护没了。
|
|
10900
11029
|
*/
|
|
10901
|
-
|
|
10902
|
-
|
|
10903
|
-
|
|
11030
|
+
declare const SettingsFileSchema: z.ZodObject<{
|
|
11031
|
+
permissions: z.ZodOptional<z.ZodObject<{
|
|
11032
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11033
|
+
ask: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11034
|
+
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11035
|
+
}, z.core.$strip>>;
|
|
11036
|
+
model: z.ZodOptional<z.ZodString>;
|
|
11037
|
+
maxTurns: z.ZodOptional<z.ZodInt>;
|
|
11038
|
+
}, z.core.$strip>;
|
|
11039
|
+
type SettingsFile = z.infer<typeof SettingsFileSchema>;
|
|
10904
11040
|
/**
|
|
10905
|
-
*
|
|
11041
|
+
* `--settings` 指的文件用不了(方案 29 §2.4 / 验收 #16)。
|
|
10906
11042
|
*
|
|
10907
|
-
*
|
|
10908
|
-
*
|
|
10909
|
-
* @param tried 本次请求里已经试过的 `provider/model`。**它是终止条件** ——
|
|
10910
|
-
* 没有它,「降到默认模型 → 默认模型也 404 → 又降到默认模型」
|
|
10911
|
-
* 会原地转圈,直到重试次数耗尽才报一个驴唇不对马嘴的错
|
|
10912
|
-
* @param configured `config.fallback.model`,用户显式指定的同家后备
|
|
10913
|
-
* @returns 没有下一个了就是 `undefined`,调用方据此升级到换 provider
|
|
10914
|
-
*/
|
|
10915
|
-
declare function nextFallbackModel(provider: ProviderType, current: string, tried: ReadonlySet<string>, configured?: string): string | undefined;
|
|
10916
|
-
/**
|
|
10917
|
-
* 连续失败到几次就放弃这个模型(方案 26 验收 #8)。
|
|
11043
|
+
* 单独一个类型的理由同 `ManagedPolicyError`:这**不是故障也不是 bug**,是用户
|
|
11044
|
+
* 显式给的那份文件坏了,CLI 要按「预期内失败」打一行(见 cli 的 `errors.ts`)。
|
|
10918
11045
|
*
|
|
10919
|
-
*
|
|
10920
|
-
*
|
|
10921
|
-
* 而一个真的挂了的模型不会在第 7 次突然好。
|
|
11046
|
+
* 为什么只有这一层抛、其余层照旧「整份忽略 + 一条 issue」:见
|
|
11047
|
+
* [sources.ts](./sources.ts) 里 `finishLayer` 那处注释。
|
|
10922
11048
|
*/
|
|
10923
|
-
declare
|
|
11049
|
+
declare class SettingsFileError extends Error {
|
|
11050
|
+
/** 下一步做什么。和 message 分开,CLI 排在第二行 */
|
|
11051
|
+
readonly hint: string;
|
|
11052
|
+
constructor(message: string, hint: string);
|
|
11053
|
+
}
|
|
10924
11054
|
/**
|
|
10925
|
-
*
|
|
11055
|
+
* JSON 解析失败的一句话,**保证带行号**。
|
|
10926
11056
|
*
|
|
10927
|
-
*
|
|
10928
|
-
*
|
|
10929
|
-
*
|
|
10930
|
-
*
|
|
10931
|
-
* ## 计数单位是「请求」不是「重试」
|
|
10932
|
-
*
|
|
10933
|
-
* 一次 `generate()` 内部为同一个模型重试三次,算 **1** 次失败而不是 3 次。
|
|
10934
|
-
* 重试是同一次意图的一部分;把它算成三次,等于第一轮就把模型判死刑。
|
|
10935
|
-
*
|
|
10936
|
-
* ## 只在本会话内有效,且用户能一句话推翻
|
|
11057
|
+
* Node 22 的 `JSON.parse` 报错里其实已经有 `(line 3 column 7)` 了,但不能靠它:
|
|
11058
|
+
* 一是旧版本只给 `position 18`,二是 `Unexpected end of JSON input` 这类压根没有
|
|
11059
|
+
* 位置。所以按 position 自己数一遍换行 —— 「第几行」是用户唯一能拿去改文件的
|
|
11060
|
+
* 信息,而 position 是个要他打开编辑器去数字节的数。
|
|
10937
11061
|
*
|
|
10938
|
-
*
|
|
10939
|
-
* 会变成一个没人知道怎么解除的诅咒。`/model` 手动切回来会调 {@link clear},
|
|
10940
|
-
* 那是显式的「我要再试一次」——用户的话永远压过引擎的推断。
|
|
11062
|
+
* 数不出来时**原样返回 Node 那句**,不编一个假的行号。
|
|
10941
11063
|
*/
|
|
10942
|
-
declare
|
|
10943
|
-
private readonly counts;
|
|
10944
|
-
private readonly abandoned;
|
|
10945
|
-
/**
|
|
10946
|
-
* 记一次失败。
|
|
10947
|
-
*
|
|
10948
|
-
* @returns 这一下是不是**刚好**跨过阈值。只在跨过的那一次回 `true`,
|
|
10949
|
-
* 调用方据此发一条通知 —— 之后每次失败都发就成了刷屏
|
|
10950
|
-
*/
|
|
10951
|
-
recordFailure(provider: ProviderType, model: string): boolean;
|
|
10952
|
-
/**
|
|
10953
|
-
* 记一次成功 —— 连续计数**清零**,不是减一。
|
|
10954
|
-
*
|
|
10955
|
-
* 「连续」是这里的关键词:跑通了十轮之后再失败一次,和连着失败三次,
|
|
10956
|
-
* 是完全不同的两件事,只有后者说明模型真的不好使了。
|
|
10957
|
-
*/
|
|
10958
|
-
recordSuccess(provider: ProviderType, model: string): void;
|
|
10959
|
-
/** 这个模型已经被放弃了吗 */
|
|
10960
|
-
isAbandoned(provider: ProviderType, model: string): boolean;
|
|
10961
|
-
/** 抹掉一个模型的全部记录。用户手动切回它时调 —— 显式意图压过引擎的推断 */
|
|
10962
|
-
clear(provider: ProviderType, model: string): void;
|
|
10963
|
-
}
|
|
11064
|
+
declare function describeJsonError(err: unknown, text: string): string;
|
|
10964
11065
|
|
|
10965
11066
|
/**
|
|
10966
|
-
*
|
|
10967
|
-
*
|
|
10968
|
-
* ## 先量的那一遍
|
|
10969
|
-
*
|
|
10970
|
-
* 改造前全仓统一走 `Math.ceil(text.length / 4)`。拿**本仓库的真实文件**过一遍
|
|
10971
|
-
* (2026-08-15,对照 `o200k_base` 的真实分词结果):
|
|
10972
|
-
*
|
|
10973
|
-
* | 样本 | chars/4 | 真实 | 真 / 估 |
|
|
10974
|
-
* | ----------------------------------- | ------- | ---- | --------- |
|
|
10975
|
-
* | AGENTS.md 里纯中文的行 | 229 | 616 | **2.69×** |
|
|
10976
|
-
* | AGENTS.md 全文(中英混排散文) | 3008 | 6035 | **2.01×** |
|
|
10977
|
-
* | core/README.md(中英混排) | 2392 | 3333 | 1.39× |
|
|
10978
|
-
* | 三个 `.ts`(代码 + 中文 JSDoc) | — | — | 1.29–1.45× |
|
|
10979
|
-
* | compressor.ts 里纯 ASCII 的行 | 2741 | 2701 | 0.99× |
|
|
10980
|
-
* | 纯英文散文 | 107 | 68 | 0.64× |
|
|
10981
|
-
*
|
|
10982
|
-
* 读法是:**英文和纯代码场景它一直是准的,中文场景系统性低估 2–2.7 倍**
|
|
10983
|
-
* (换成 `cl100k_base` 那种老编码更差,纯中文到 3.66×)。而 `/context` 那张
|
|
10984
|
-
* 占用表、路由的「这一轮塞不塞得下」、压缩器的尾部预算全都吃这个数 ——
|
|
10985
|
-
* 所以它不是显示精度问题,是三处功能的地基。
|
|
10986
|
-
*
|
|
10987
|
-
* ## 为什么挑 gpt-tokenizer
|
|
10988
|
-
*
|
|
10989
|
-
* 三个候选实测(Windows / Node 24,各起**新进程**取 5 次中位数,
|
|
10990
|
-
* 量的是「从 import 到算出第一个数」)。判据按这个顺序:
|
|
10991
|
-
*
|
|
10992
|
-
* 1. **不能拖慢冷启动** —— TUI 和 CLI 每次都要起。
|
|
10993
|
-
* `gpt-tokenizer/encoding/o200k_base` **89 ms**,
|
|
10994
|
-
* `js-tiktoken/lite` + `ranks/o200k_base` **289 ms**,3.2 倍差距。
|
|
10995
|
-
* 而且 gpt-tokenizer 按编码分入口,另外五套词表一个字节都不会被解析。
|
|
10996
|
-
* 再加上下面的懒加载,进程启动本身**一毫秒都不多花**
|
|
10997
|
-
* 2. **不能引入 wasm / native 附件** —— `@dqbd/tiktoken` 是 wasm,直接出局:
|
|
10998
|
-
* `pnpm pack` / `verify:pack` 那条路会变复杂,Windows 上装 native 附件
|
|
10999
|
-
* 也是本仓库的已知坑。gpt-tokenizer 是纯 JS、**零运行时依赖**
|
|
11000
|
-
* 3. **体积** —— 这一条 gpt-tokenizer 是**输**的:装完 54 MB,js-tiktoken 22 MB。
|
|
11001
|
-
* 但真正被 require 进内存的只有 `esm/encoding/o200k_base.js` 那 2.3 MB,
|
|
11002
|
-
* 而判据顺序里冷启动排在体积前面,所以认这个亏
|
|
11003
|
-
* 4. **许可证** —— MIT。**只是依赖它,没有抄任何一行代码**,所以本文件
|
|
11004
|
-
* 不加 `SPDX-License-Identifier` 头(AGENTS.md 铁律 12:那个头在本仓库的
|
|
11005
|
-
* 含义是「抄自 gemini-cli」,加了就必须同步登记进 NOTICE)
|
|
11006
|
-
*
|
|
11007
|
-
* ## 一套编码走天下,这是**有意的近似**
|
|
11008
|
-
*
|
|
11009
|
-
* 模型不同分词器就不同(GPT 的 o200k / cl100k、Claude 自己的 BPE、DeepSeek
|
|
11010
|
-
* 自己的),但今天**所有模型都走 `o200k_base` 这一套**。挑它是因为它是当代
|
|
11011
|
-
* 编码里 CJK 合并最充分的,拿来当 Claude / DeepSeek 的代理比 `cl100k_base` 近。
|
|
11012
|
-
* 真按模型分套要各家的词表文件,那是另一轮的事 —— 这一轮只把口径统一到一处。
|
|
11013
|
-
*
|
|
11014
|
-
* 接口上留了 `model` 形参,今天走到 {@link encodingFor} 就被忽略掉;
|
|
11015
|
-
* 留着是为了将来加分支时**调用方一行都不用改**。
|
|
11016
|
-
*
|
|
11017
|
-
* ⚠️ **它仍然是估算。** 真值以 provider 回的 usage 为准(那本来就是准的,
|
|
11018
|
-
* 见 `cost/pricing.ts` 的 `promptTokens`)。别拿这个数去断言「等于 usage」。
|
|
11019
|
-
*
|
|
11020
|
-
* ## 2026-08-15:换了真分词器**不等于**可以摘掉界面上那枚「估算」徽标
|
|
11021
|
-
*
|
|
11022
|
-
* 这一条写在这儿是因为它已经被读反过一次。`design/web-ui/index.html:744` 那条
|
|
11023
|
-
* HTML 注释末尾写着「要拿掉这个徽标得先换真 tokenizer」—— 它是**必要条件**,
|
|
11024
|
-
* 不是充分条件,而同一份稿子的 `thread.css` 里 `.est` 那条写死了充分条件:
|
|
11025
|
-
* 「token 有三个时刻,只有**运行前那一个**是猜的,所以这个徽标只准出现在
|
|
11026
|
-
* `.mhead__sub` 里」。
|
|
11027
|
-
*
|
|
11028
|
-
* 换句话说:这个文件治好的是**偏差有多大**(中文场景 2–2.7 倍的系统性低估),
|
|
11029
|
-
* 治不好的是**它是不是猜的**。后者由两件事钉死,两件都还在:
|
|
11067
|
+
* 企业托管设置 —— 第 ∞ 层(方案 22 §2.6)。
|
|
11030
11068
|
*
|
|
11031
|
-
*
|
|
11032
|
-
*
|
|
11033
|
-
*
|
|
11034
|
-
* 插进压缩、后台任务通知、工具结果。
|
|
11069
|
+
* 由 IT 管理员分发到一个**普通用户写不了**的机器级路径
|
|
11070
|
+
* (见 infra 的 `managedSettingsPath()`),压过其余全部五层,且额外带三个
|
|
11071
|
+
* **元开关** —— 它们管的不是「配了什么」,而是「别的层还算不算数」:
|
|
11035
11072
|
*
|
|
11036
|
-
*
|
|
11037
|
-
*
|
|
11073
|
+
* | 开关 | 作用 |
|
|
11074
|
+
* | -------------------------------- | ------------------------------------------ |
|
|
11075
|
+
* | `allowManagedPermissionRulesOnly` | 其余各层的 allow / ask / deny 全部忽略 |
|
|
11076
|
+
* | `allowManagedHooksOnly` | 用户级 hook 一律不加载 |
|
|
11077
|
+
* | `disableBypassPermissionsMode` | `permission: bypass` 拒绝启动 |
|
|
11038
11078
|
*
|
|
11039
|
-
* ##
|
|
11079
|
+
* ## 这一层**不过信任闸门**,而且不该过
|
|
11040
11080
|
*
|
|
11041
|
-
*
|
|
11042
|
-
*
|
|
11043
|
-
*
|
|
11081
|
+
* 闸门问的是「这个仓库的作者可不可信」。托管设置不来自仓库,来自这台机器的
|
|
11082
|
+
* 管理员 —— 拿仓库信任去闸它没有意义,而且方向是反的:托管策略的典型内容是
|
|
11083
|
+
* deny,越是不信任的目录越需要它生效。
|
|
11044
11084
|
*
|
|
11045
|
-
*
|
|
11046
|
-
* `index.html` 那枚「估算」徽标上面那段注释同改(它是同一句话的第二处)。
|
|
11047
|
-
* - `.agents/plans/README.md`「42 划出来的底座缺口」里那一条划掉。
|
|
11085
|
+
* ## 三个开关都是「只减不增」,所以缺省一律 false
|
|
11048
11086
|
*
|
|
11049
|
-
*
|
|
11050
|
-
*
|
|
11051
|
-
*
|
|
11052
|
-
*
|
|
11087
|
+
* 读不到文件、文件写坏了、字段没写 —— 一律按「没有托管策略」处理。
|
|
11088
|
+
* 反过来(读不到就当全开)会让一次磁盘故障把所有人的 epoch 锁死,
|
|
11089
|
+
* 而托管策略保护的东西并不值这个代价:它挡的是用户给自己提权,
|
|
11090
|
+
* 不是挡攻击者(能改 `C:\ProgramData` 的人早就能改 epoch 本身了)。
|
|
11053
11091
|
*/
|
|
11092
|
+
|
|
11054
11093
|
/**
|
|
11055
|
-
*
|
|
11094
|
+
* 托管文件的形状 = 普通设置文件 + 三个元开关。
|
|
11056
11095
|
*
|
|
11057
|
-
*
|
|
11058
|
-
*
|
|
11096
|
+
* 用 `.extend()` 而不是另写一份:托管层能配的普通键必须和其它层完全一致,
|
|
11097
|
+
* 否则「管理员配了 model 但只有托管层不生效」这种事没人查得出来。
|
|
11059
11098
|
*/
|
|
11060
|
-
declare
|
|
11061
|
-
|
|
11099
|
+
declare const ManagedSettingsSchema: z.ZodObject<{
|
|
11100
|
+
permissions: z.ZodOptional<z.ZodObject<{
|
|
11101
|
+
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11102
|
+
ask: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11103
|
+
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11104
|
+
}, z.core.$strip>>;
|
|
11105
|
+
model: z.ZodOptional<z.ZodString>;
|
|
11106
|
+
maxTurns: z.ZodOptional<z.ZodInt>;
|
|
11107
|
+
allowManagedPermissionRulesOnly: z.ZodOptional<z.ZodBoolean>;
|
|
11108
|
+
allowManagedHooksOnly: z.ZodOptional<z.ZodBoolean>;
|
|
11109
|
+
disableBypassPermissionsMode: z.ZodOptional<z.ZodBoolean>;
|
|
11110
|
+
}, z.core.$strip>;
|
|
11111
|
+
type ManagedSettings = z.infer<typeof ManagedSettingsSchema>;
|
|
11062
11112
|
/**
|
|
11063
|
-
*
|
|
11113
|
+
* 托管策略的最终形态 —— 三个开关 + 「这台机器上到底有没有托管设置」。
|
|
11064
11114
|
*
|
|
11065
|
-
*
|
|
11066
|
-
*
|
|
11067
|
-
*
|
|
11068
|
-
* 现在 base URL 全部与 `@ai-sdk/*` 各 provider 的默认 chat baseURL 对齐(同一
|
|
11069
|
-
* 个域名下发现和调用才可能一致),`/v1` 有无由候选重试决定而不是靠猜。
|
|
11115
|
+
* `present` 单独留着是因为它和「三个开关全 false」不是一回事:前者是
|
|
11116
|
+
* 「管理员没管这台机器」,后者是「管理员管了但只下发了几条 deny」。
|
|
11117
|
+
* 诊断措辞和 `/permissions` 面板都要区分这两种。
|
|
11070
11118
|
*/
|
|
11071
|
-
interface
|
|
11072
|
-
|
|
11073
|
-
|
|
11119
|
+
interface ManagedPolicy {
|
|
11120
|
+
/** 读的是哪个文件。哪怕不存在也带着 —— 管理员要知道该往哪儿放 */
|
|
11121
|
+
path: string;
|
|
11122
|
+
present: boolean;
|
|
11123
|
+
allowManagedPermissionRulesOnly: boolean;
|
|
11124
|
+
allowManagedHooksOnly: boolean;
|
|
11125
|
+
disableBypassPermissionsMode: boolean;
|
|
11074
11126
|
}
|
|
11127
|
+
/** 没有托管设置时的策略。**三个开关全 false**,理由见文件头 */
|
|
11128
|
+
declare function noManagedPolicy(path: string): ManagedPolicy;
|
|
11075
11129
|
/**
|
|
11076
|
-
*
|
|
11130
|
+
* 三个开关里**打开了的**那几个,逐条一句人话。全关时是空数组。
|
|
11077
11131
|
*
|
|
11078
|
-
*
|
|
11079
|
-
*
|
|
11080
|
-
*
|
|
11081
|
-
*
|
|
11132
|
+
* 开了的必须逐个说出来 —— 验收 #19 要的就是「诊断里说明」,而「我的 allow
|
|
11133
|
+
* 规则怎么全没了」正是这几个开关最典型的现场表现:不说,用户就只能怀疑是 bug。
|
|
11134
|
+
*
|
|
11135
|
+
* 回数组而不是拼好的字符串:`/permissions` 面板要逐行画,启动诊断要一行 ——
|
|
11136
|
+
* 拼接的活交给各自的呈现方,措辞只有这一处。
|
|
11082
11137
|
*/
|
|
11083
|
-
declare function
|
|
11138
|
+
declare function managedSwitchNotes(policy: ManagedPolicy): string[];
|
|
11084
11139
|
/**
|
|
11085
|
-
*
|
|
11086
|
-
*
|
|
11087
|
-
*
|
|
11088
|
-
*
|
|
11089
|
-
* (hermes models.py:5148 踩过这个坑,注释里挂着 gemini-cli #12532)
|
|
11140
|
+
* 托管策略挡下了一件事时抛的错。
|
|
11141
|
+
*
|
|
11142
|
+
* 单独一个类型是为了让 CLI 能把它和「模块起不来」区分开:这不是故障,
|
|
11143
|
+
* 是**策略生效了**,退出码和措辞都该不一样(用户要做的是改配置,不是报 bug)。
|
|
11090
11144
|
*/
|
|
11091
|
-
declare
|
|
11145
|
+
declare class ManagedPolicyError extends Error {
|
|
11146
|
+
readonly code = "MANAGED_POLICY";
|
|
11147
|
+
/** 用户接下来该干什么。和 message 分开,宿主可以排在第二行 */
|
|
11148
|
+
readonly hint: string;
|
|
11149
|
+
constructor(message: string, hint: string);
|
|
11150
|
+
}
|
|
11092
11151
|
/**
|
|
11093
|
-
*
|
|
11152
|
+
* `disableBypassPermissionsMode` 的执行点(验收 #20)。
|
|
11094
11153
|
*
|
|
11095
|
-
*
|
|
11096
|
-
*
|
|
11097
|
-
*
|
|
11154
|
+
* ## 为什么是**拒绝启动**而不是静默降级到 default
|
|
11155
|
+
*
|
|
11156
|
+
* 降级是「更安全」的那一侧,但它会让用户以为自己在 bypass 里 —— 然后每一次
|
|
11157
|
+
* 审批弹窗都成了怪现象,而真正的原因(IT 禁掉了这个级别)一个字都看不见。
|
|
11158
|
+
* 权限级别是用户**显式**要的东西(`--permission bypass` 或 config.yaml 里那一行),
|
|
11159
|
+
* 显式请求被策略挡下必须当面说,这跟「配了个不认识的级别回退到 default」
|
|
11160
|
+
* 那种笔误不是一回事。
|
|
11161
|
+
*
|
|
11162
|
+
* 挡的是级别本身,不区分它从哪一层来 —— `--permission`、`EPOCH_PERMISSION`、
|
|
11163
|
+
* `~/.epoch/config.yaml` 最后都汇进同一个 `config.permission`,
|
|
11164
|
+
* 在这里分叉等于把「哪个来源算数」这个问题又答一遍。
|
|
11098
11165
|
*/
|
|
11099
|
-
declare function
|
|
11100
|
-
/** 清除 provider 的模型缓存 */
|
|
11101
|
-
declare function clearModelCache(homeDir: string, provider: string): void;
|
|
11166
|
+
declare function assertLevelAllowed(level: PermissionLevel, policy: ManagedPolicy): void;
|
|
11102
11167
|
|
|
11103
11168
|
/**
|
|
11104
|
-
*
|
|
11169
|
+
* 设置来源分层 —— 五层发现、合并、信任闸门(方案 22 §2.4 / §2.5)。
|
|
11105
11170
|
*
|
|
11106
|
-
*
|
|
11107
|
-
*
|
|
11108
|
-
*
|
|
11171
|
+
* ```
|
|
11172
|
+
* ① 内置默认 schema 默认值 ┐
|
|
11173
|
+
* ①ᐟ⁵ 插件 ~/.epoch/plugins/<插件>/settings.json ← **只有 deny 生效**(方案 32)
|
|
11174
|
+
* ② 用户级 ~/.epoch/config.yaml ┘ 这两层由 loader.ts 交出来,是本文件的入参 base
|
|
11175
|
+
* ③ 项目级 <项目根>/.epoch/settings.json ← 信任闸门
|
|
11176
|
+
* ④ 项目本地 <项目根>/.epoch/settings.local.json ← 信任闸门
|
|
11177
|
+
* ⑤ 命令行 --settings <file>
|
|
11178
|
+
* ∞ 企业托管 机器级 managed-settings.json ← 不过闸门,见 managed.ts
|
|
11179
|
+
* ```
|
|
11109
11180
|
*
|
|
11110
|
-
*
|
|
11111
|
-
* - token 上限与成本上限**任一**触顶即触顶
|
|
11112
|
-
* - 无定价数据时看 `onUnknownPricing`:
|
|
11113
|
-
* 'warn'(默认)token 上限照常生效、成本上限跳过,并且只告警一次
|
|
11114
|
-
* 'block' 当作触顶(前提是用户确实配了 maxCostUsd,
|
|
11115
|
-
* 否则「没配预算」不该因为查不到定价就把人拦下来)
|
|
11116
|
-
* - 一个 budget 字段都没配 → 永不触顶,此时闸门退化成纯记账器
|
|
11181
|
+
* ## 合并语义只有两条
|
|
11117
11182
|
*
|
|
11118
|
-
*
|
|
11183
|
+
* - `permissions.allow/ask/deny` 三张列表**取并集**(去重,保序)。
|
|
11184
|
+
* 项目想加一条 deny,不该被迫把用户那张 deny 抄一遍
|
|
11185
|
+
* - 其余键**覆盖**,高层级赢
|
|
11119
11186
|
*
|
|
11120
|
-
*
|
|
11121
|
-
* 恢复,跨进程、跨会话累加。`maxCallCostUsd`(`--max-budget-usd`)是「这一次别超」,
|
|
11122
|
-
* 计的是 `callMicros`:**从这个闸门实例被造出来那一刻起算,永不恢复、永不持久化**。
|
|
11187
|
+
* ## 为什么不写在 loader.ts 里
|
|
11123
11188
|
*
|
|
11124
|
-
*
|
|
11125
|
-
*
|
|
11189
|
+
* 项目级要过信任闸门,而闸门要 `config.homeDir`(信任记录存那儿)和
|
|
11190
|
+
* `config.trust.enabled` —— 两者都得先把用户级配置解析完才有。塞进 `loadConfig()`
|
|
11191
|
+
* 就成了「加载配置需要先加载配置」。所以做成**加载后的第二相**:
|
|
11192
|
+
* 装配层拿到 base config、判完信任,再调这里。
|
|
11193
|
+
*
|
|
11194
|
+
* ## 只放行三个键,不是偷懒
|
|
11195
|
+
*
|
|
11196
|
+
* `permissions` / `model` / `maxTurns`。刻意排除的:
|
|
11197
|
+
*
|
|
11198
|
+
* - **`permission`(级别)** —— 让仓库能写 `permission: bypass` 的话,
|
|
11199
|
+
* `epoch trust add` 就从「允许读这个仓库的指令文件」变成了一个隐式的提权开关,
|
|
11200
|
+
* 正是 [trust/gate.ts](../trust/gate.ts) 文件头写着要避免的事
|
|
11201
|
+
* - **`shell`** —— 它在装配之前就被 `applyShell()` 消费掉了,放进来只会得到
|
|
11202
|
+
* 一个「配了但不生效」的键
|
|
11203
|
+
* - **`hooks` / `provider`** —— hook 会 spawn 子进程,等价于任意代码执行;
|
|
11204
|
+
* provider 里有凭据。这两个哪怕已信任也不该由仓库说了算
|
|
11205
|
+
* - **`compression`(2026-08-16 加这一段时判的)** —— 见下面那一节
|
|
11206
|
+
*
|
|
11207
|
+
* 想加键就往 {@link SettingsFileSchema} 里加,并且必须回答「仓库作者拿它能做什么」。
|
|
11208
|
+
*
|
|
11209
|
+
* ## 一次真的照这条判据走了一遍:`compression` 判**不进**白名单
|
|
11210
|
+
*
|
|
11211
|
+
* 2026-08-16 给 `EpochConfig` 补了 `compression.enabled` / `compression.threshold`
|
|
11212
|
+
* (设计稿「设置 › 记忆与上下文」那个连着两轮记「只记不改」的空壳开关)。
|
|
11213
|
+
* 那一轮顺手要回答的问题是:这两个键要不要也放进白名单。**判不进**,三条:
|
|
11214
|
+
*
|
|
11215
|
+
* 1. **上面那个问题有答案,而答案是坏的。** 仓库作者拿 `compression.enabled: false`
|
|
11216
|
+
* 能做的是:让**别人**在这个仓库里开的长会话悄悄撞满上下文窗口。症状是 provider
|
|
11217
|
+
* 回 400,而现场离这个键十万八千里 —— 用户会以为是我们坏了。
|
|
11218
|
+
* 拿 `threshold: 1` 能做同一件事,而且更隐蔽(开关还写着「开」)。
|
|
11219
|
+
* 反过来 `threshold: 0.01` 是让每一轮都调一次 utility 模型,**花的是用户的钱**。
|
|
11220
|
+
* ⚠️ 注意这条和 `permission` 那一条**不是**同一种坏:那一条是提权,这一条是
|
|
11221
|
+
* 「让别人的会话以一种查不出来的方式变坏」。两者都够格排除,理由要分开写 ——
|
|
11222
|
+
* 合成一句「不安全」的话,下一个人判新键时没有可比的先例
|
|
11223
|
+
* 2. **它和它的两个邻居同级。** `contextLength` 和 `maxTurns` 是设置页
|
|
11224
|
+
* 「记忆与上下文」那一节里紧挨着的两行,而 `contextLength` **也不在白名单里**。
|
|
11225
|
+
* 压缩阈值是 `contextLength` 的一个系数(乘出来才是那条线),
|
|
11226
|
+
* 让系数比被乘数更容易被仓库改,是个说不出理由的口子
|
|
11227
|
+
* 3. **写路径今天也接不住它。** `compression.enabled` 是**嵌套键**,而
|
|
11228
|
+
* [write.ts](./write.ts) 的 `writeUserYaml()` 只认不带缩进的顶层 `key:`
|
|
11229
|
+
* (`spliceTopLevel`)。放进白名单等于让设置页画出一个保存按钮,
|
|
11230
|
+
* 按下去回 `unwritable-file` —— 决定 20 ① 骂的正是这个形状。
|
|
11231
|
+
* ⚠️ 这一条**是三条里最弱的**:它描述的是今天的实现,不是设计判断。
|
|
11232
|
+
* 真要收它,前两条才是要被推翻的东西;只把 `spliceTopLevel` 改成认嵌套键
|
|
11233
|
+
* 并不足以让这个键进白名单
|
|
11234
|
+
*
|
|
11235
|
+
* 于是这两行在设置页上是**只读**的(`overridable: false` / `chain: []` /
|
|
11236
|
+
* `writes: []`),和 `contextLength` / `budget.*` 逐字同一种形态:
|
|
11237
|
+
* 如实印出值和来源层,然后说出改它的真正路径(`~/.epoch/config.yaml`)。
|
|
11126
11238
|
*/
|
|
11127
11239
|
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11240
|
+
/**
|
|
11241
|
+
* 由**文件**提供的那几层,**顺序即优先级**(`managed` 最高)。反查来源层的两处
|
|
11242
|
+
* (`buildPermissionsControl` 的规则表、`explainSetting` 的标量链条)都照这个顺序
|
|
11243
|
+
* 从后往前取「最高的那一层」。
|
|
11244
|
+
*
|
|
11245
|
+
* **这个数组就是那张顺序表本身,别再抄第二份。** 2026-08-15 之前是「这里一个联合
|
|
11246
|
+
* 类型 + `buildPermissionsControl` 里一个字面量数组」,靠两句注释要求同步;现在
|
|
11247
|
+
* {@link SettingsLayer} 由它派生 —— 加一层而漏了这张表,那个新值压根不在联合类型
|
|
11248
|
+
* 里,**当场编译不过**。漏一层的后果是那层的规则 / 标量被反查成 `unknown`,
|
|
11249
|
+
* 于是面板告诉用户「不知道这个值从哪来」,正好是它存在的理由本身。
|
|
11250
|
+
*/
|
|
11251
|
+
declare const SETTINGS_LAYER_ORDER: readonly ["plugin", "user", "project", "projectLocal", "cli", "managed"];
|
|
11252
|
+
/** 由**文件**提供的那几层(用户级由 loader 负责,内置默认在 schema 里) */
|
|
11253
|
+
type SettingsLayer = (typeof SETTINGS_LAYER_ORDER)[number];
|
|
11254
|
+
/**
|
|
11255
|
+
* 这一层最后怎么了。
|
|
11256
|
+
*
|
|
11257
|
+
* `gated` 是本方案独有的一档:文件读到了、也解析了,但目录没被信任,
|
|
11258
|
+
* 于是**只有 deny 被采纳**。它既不是 `loaded` 也不是 `missing`,
|
|
11259
|
+
* 混进任何一档都会让启动诊断说谎。
|
|
11260
|
+
*/
|
|
11261
|
+
type LayerStatus = 'loaded' | 'gated' | 'missing' | 'failed';
|
|
11262
|
+
/**
|
|
11263
|
+
* 设置文件里的**标量**键 —— `permissions` 不在这里,它走并集不走覆盖。
|
|
11264
|
+
*
|
|
11265
|
+
* 这两个加上 `permissions` 就是 {@link SettingsFileSchema} 的全部(白名单的判据
|
|
11266
|
+
* 见文件头),所以「哪些键能被项目级压回去」这个问题的答案是**闭的** ——
|
|
11267
|
+
* 设置页据此决定哪几行挂得起六层链条,剩下的行只可能有 ① 和 ②。
|
|
11268
|
+
*/
|
|
11269
|
+
declare const SCALAR_KEYS: readonly ["model", "maxTurns"];
|
|
11270
|
+
type ScalarKey = (typeof SCALAR_KEYS)[number];
|
|
11271
|
+
/** 一层写进去的标量。没写的键**整个缺席**,不是 `undefined` —— 两者在链条上是两行 */
|
|
11272
|
+
type LayerScalars = {
|
|
11273
|
+
readonly [K in ScalarKey]?: SettingsFile[K];
|
|
11274
|
+
};
|
|
11275
|
+
interface LayerOutcome {
|
|
11276
|
+
layer: SettingsLayer;
|
|
11277
|
+
/** 文件路径。`user` 层是 `~/.epoch/config.yaml` */
|
|
11278
|
+
path: string;
|
|
11279
|
+
status: LayerStatus;
|
|
11280
|
+
/** 这一层**最终采纳**的规则(`gated` 时只剩 deny),PR-4 的来源标注要用 */
|
|
11281
|
+
lists: PermissionRuleLists;
|
|
11137
11282
|
/**
|
|
11138
|
-
*
|
|
11283
|
+
* 这一层**实际覆盖掉**的标量键(方案 43 §七的来源标注要用)。真源是
|
|
11284
|
+
* {@link applyScalars} 的返回值 —— 那是全流程唯一往 `config` 上写标量的地方,
|
|
11285
|
+
* 所以「这一层贡献了什么」和「这一层真的写进去了什么」不可能分叉。未信任的 ③④
|
|
11286
|
+
* 过完闸门已经没有标量了,插件层(①ᐟ⁵)压根不调 `applyScalars`,两者都是空对象。
|
|
11139
11287
|
*
|
|
11140
|
-
*
|
|
11141
|
-
*
|
|
11288
|
+
* ⚠️ **`user` 层这里恒为空**,那不是漏了:用户级是 loader 交出来的 base,它已经
|
|
11289
|
+
* 把 schema 默认值和 `config.yaml` 合并过了,`resolveSettings` 手上分不出 ① 和
|
|
11290
|
+
* ②。那一刀由 loader 的 `getConfigOrigins()` 切,见 [provenance.ts](./provenance.ts)。
|
|
11142
11291
|
*/
|
|
11143
|
-
|
|
11292
|
+
scalars: LayerScalars;
|
|
11293
|
+
/** 可直接写进 diagnostics 的一句话;`missing` 时是 undefined —— 没配过不用说 */
|
|
11294
|
+
detail?: string;
|
|
11144
11295
|
}
|
|
11145
|
-
|
|
11146
|
-
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11150
|
-
/** 本次调用的金额上限(微美元)。没给就是 undefined = 不限 */
|
|
11151
|
-
private readonly callCapMicros;
|
|
11152
|
-
private usage;
|
|
11153
|
-
/** 已知定价部分的累计花费(微美元)。从没拿到过定价时为 undefined */
|
|
11154
|
-
private micros;
|
|
11155
|
-
/**
|
|
11156
|
-
* 造出来那一刻的累计花费(微美元),本次调用的**起算线**。
|
|
11157
|
-
*
|
|
11158
|
-
* 取差值而不是另记一个「本次加了多少」的计数器,是为了把 `delegate_task`
|
|
11159
|
-
* 派出去的那笔算进来:子 agent 是另一个闸门、只往 `BudgetStore` 里写,
|
|
11160
|
-
* 父这边靠 `syncFromStore()` 采纳。自己单独累加就只看得见父进程发的请求,
|
|
11161
|
-
* 而那笔钱**确实是这一次调用花的**。
|
|
11162
|
-
*/
|
|
11163
|
-
private readonly baselineMicros;
|
|
11164
|
-
/** 是否出现过「有用量但查不到定价」的轮次 */
|
|
11165
|
-
private sawUnknownPricing;
|
|
11166
|
-
private warned;
|
|
11167
|
-
private unknownPricingWarned;
|
|
11168
|
-
constructor(opts?: BudgetGuardOptions);
|
|
11169
|
-
/**
|
|
11170
|
-
* 本次调用花掉的(微美元)。
|
|
11171
|
-
*
|
|
11172
|
-
* 从没拿到过定价时是 0 —— 「不知道花了多少」不构成拦人的理由,
|
|
11173
|
-
* 和 `breachReason()` 里成本上限遇到未知定价的处置完全一致
|
|
11174
|
-
* (那条路归 `onUnknownPricing` 管,不在这里另开一套语义)。
|
|
11175
|
-
*/
|
|
11176
|
-
private callSpentMicros;
|
|
11177
|
-
/**
|
|
11178
|
-
* 累加一次用量,返回累加后的状态。
|
|
11179
|
-
*
|
|
11180
|
-
* 第二个参数有三种形态,**三种语义各不相同**(方案 19 PR-1):
|
|
11181
|
-
*
|
|
11182
|
-
* | 调用形态 | 用哪个单价 | 为什么 |
|
|
11183
|
-
* | --------------------------------- | ----------------- | ----------------------------------------- |
|
|
11184
|
-
* | `add(usage)` | `defaultPricing` | 没说用了哪个模型 = 就是配置里那个 |
|
|
11185
|
-
* | `add(usage, { model, pricing })` | `pricing` | 明确知道用了谁、也知道多少钱 |
|
|
11186
|
-
* | `add(usage, { model })` | **不回落,记未知** | 知道用了谁,但查不到它的价 |
|
|
11187
|
-
*
|
|
11188
|
-
* 第三行是这个签名存在的全部理由。改造前的形态是
|
|
11189
|
-
* `add(usage, pricing?: ModelPricing)`,`pricing ?? this.defaultPricing`
|
|
11190
|
-
* 会把「查不到」悄悄换成「主模型的价」—— 而降级之后主模型的价是**错的**,
|
|
11191
|
-
* 且错得很有说服力(能算出一个像样的数字,闸门照常运转,只是永远拦不住)。
|
|
11192
|
-
*
|
|
11193
|
-
* 拿一个已知是错的数字继续算,比承认不知道更危险。
|
|
11194
|
-
*/
|
|
11195
|
-
add(usage: TokenUsage, rate?: {
|
|
11196
|
-
model: string;
|
|
11197
|
-
pricing?: ModelPricing;
|
|
11198
|
-
}): BudgetStatus;
|
|
11199
|
-
/** 是否已触顶。触顶后不该再发起下一次模型请求 */
|
|
11200
|
-
exceeded(): boolean;
|
|
11201
|
-
/**
|
|
11202
|
-
* 从存储重新同步累计,只在持久化的数字**更大**时采纳。
|
|
11203
|
-
*
|
|
11204
|
-
* 为什么需要:`delegate_task` 的子 agent 是另一个 `AgentLoop`、另一个
|
|
11205
|
-
* `BudgetGuard`,只是共用同一个 sessionId。父 guard 内存里持着自己那份累计,
|
|
11206
|
-
* 子任务跑完往文件里写了「父 + 子」,父的下一次 `persist()` 会拿旧的内存值
|
|
11207
|
-
* 盖回去——子任务的花费凭空消失,上限被稀释。
|
|
11208
|
-
*
|
|
11209
|
-
* 取「更大者」而不是无条件采纳:同一个会话里累计只增不减,所以更大的那份
|
|
11210
|
-
* 一定是更新的。这样也顺带扛住了别的进程同时在写同一个会话(虽然正常不该发生)。
|
|
11211
|
-
*
|
|
11212
|
-
* 调用点在**发起请求之前**,和触顶判定同一个位置——同步完再判,才判得准。
|
|
11213
|
-
*/
|
|
11214
|
-
syncFromStore(): void;
|
|
11215
|
-
/**
|
|
11216
|
-
* 触顶原因,没触顶就是 undefined。
|
|
11217
|
-
*
|
|
11218
|
-
* 单独暴露而不是让调用方去读 status():status() 会消费掉「只报一次」的
|
|
11219
|
-
* 标记,只想问一句「能不能继续」的地方不该有副作用。
|
|
11220
|
-
*
|
|
11221
|
-
* **跨会话那三条判在前,`call-cost` 判在最后**:同一轮两把尺子都触到时,
|
|
11222
|
-
* 跨会话那条是更硬的墙(调高单次上限也过不去),先说更硬的那个。
|
|
11223
|
-
*/
|
|
11224
|
-
breachReason(): BudgetBreach | undefined;
|
|
11225
|
-
/** 跨会话累计那三条 */
|
|
11226
|
-
private sessionBreach;
|
|
11227
|
-
/** 本次调用那一条(`--max-budget-usd`) */
|
|
11228
|
-
private callBreach;
|
|
11229
|
-
/**
|
|
11230
|
-
* **预检**:再加 `extraTokens` 个 input token 会不会触顶。不会就是 undefined。
|
|
11231
|
-
*
|
|
11232
|
-
* `breachReason()` 回答的是「已经花超了吗」,只看得见 provider 事后报回来的
|
|
11233
|
-
* 数字。而图片是唯一一种**发出去之前就能估出量级**的开销(见
|
|
11234
|
-
* `context/image-tokens.ts`),所以它值得单独有个预检点:一张 4K 截图动辄
|
|
11235
|
-
* 上千 token,等 usage 回来再说,钱已经花了。
|
|
11236
|
-
*
|
|
11237
|
-
* 三条刻意的取舍:
|
|
11238
|
-
* - `extraTokens <= 0` 直接放行 —— 估不出来的输入在上游已经折成 0,
|
|
11239
|
-
* 「不知道」不构成拦人的理由
|
|
11240
|
-
* - 定价查不到时**不拦**成本上限:那条路归 `onUnknownPricing` 管
|
|
11241
|
-
* (见 `breachReason()`),预检不该另开一套「查不到就拦」的语义
|
|
11242
|
-
* - 只按 input 单价折算:图片进的是 input 侧,输出多少这里无从知道
|
|
11243
|
-
*/
|
|
11244
|
-
projectedBreach(extraTokens: number): BudgetBreach | undefined;
|
|
11245
|
-
/**
|
|
11246
|
-
* 预检不通过时给用户的一句人话。
|
|
11247
|
-
*
|
|
11248
|
-
* 和 `describeBreach()` 分开写,因为要说的不是同一件事:那边是「已经花超了,
|
|
11249
|
-
* 停」,这边是「**还没发**,发了才会超,所以没发」。把「未发送、未计费」写进
|
|
11250
|
-
* 文案是必须的 —— 否则用户看到预算提示的第一反应是「我这次的钱白花了」。
|
|
11251
|
-
*/
|
|
11252
|
-
describeProjectedBreach(breach: BudgetBreach, extraTokens: number): string;
|
|
11253
|
-
/**
|
|
11254
|
-
* 剩余额度。对应上限没配就是 undefined。
|
|
11255
|
-
*
|
|
11256
|
-
* 两把尺子都配了时 `costUsd` 报**更紧的那一份** —— 宿主拿它画进度条,
|
|
11257
|
-
* 显示一个先到不了的数字等于没显示。
|
|
11258
|
-
*/
|
|
11259
|
-
remaining(): {
|
|
11260
|
-
costUsd?: number;
|
|
11261
|
-
tokens?: number;
|
|
11262
|
-
};
|
|
11296
|
+
interface SettingsResolution {
|
|
11297
|
+
/** 合并后的配置。入参 `base` 不会被改动 */
|
|
11298
|
+
config: EpochConfig;
|
|
11299
|
+
layers: LayerOutcome[];
|
|
11300
|
+
issues: ParseIssue[];
|
|
11263
11301
|
/**
|
|
11264
|
-
*
|
|
11265
|
-
*
|
|
11266
|
-
* ⚠️ 算术**借 {@link persistedUsage}**,不在这儿再写一遍:没有 guard 的时候
|
|
11267
|
-
* (上一个进程留下的会话)同一个数要从落盘那条记录里重建,两处必须逐字相同。
|
|
11268
|
-
* 判据全文在那个函数上。
|
|
11302
|
+
* 第 ∞ 层的三个元开关(§2.6)。**永远非空** —— 没有托管文件时是
|
|
11303
|
+
* `noManagedPolicy()`,三个开关全 false。调用方因此不用为「有没有托管」分支
|
|
11269
11304
|
*/
|
|
11270
|
-
|
|
11305
|
+
managed: ManagedPolicy;
|
|
11306
|
+
}
|
|
11307
|
+
/** 一个插件带来的 `settings.json`(方案 32)。`plugin` 只用于诊断措辞 */
|
|
11308
|
+
interface PluginSettingsFile {
|
|
11309
|
+
plugin: string;
|
|
11310
|
+
path: string;
|
|
11311
|
+
}
|
|
11312
|
+
interface ResolveSettingsOptions {
|
|
11313
|
+
/** 第 5 层:`--settings <file>`。用户在命令行显式给的,**不过信任闸门** */
|
|
11314
|
+
settingsPath?: string;
|
|
11271
11315
|
/**
|
|
11272
|
-
*
|
|
11316
|
+
* 第 1.5 层:插件的 `settings.json`,来自 `loadPlugins().settingsPaths`。
|
|
11273
11317
|
*
|
|
11274
|
-
*
|
|
11275
|
-
* 的同时标记就被消费掉了,再调用一次就是 false。告警是给人看的,
|
|
11276
|
-
* 每轮刷一遍屏就没人看了。
|
|
11318
|
+
* 优先级在用户级**之下**,且只有 `deny` 生效 —— 见 {@link loadPluginLayer}。
|
|
11277
11319
|
*/
|
|
11278
|
-
|
|
11279
|
-
/** 触顶时给用户的一句人话 */
|
|
11280
|
-
describeBreach(breach: BudgetBreach): string;
|
|
11320
|
+
pluginSettings?: readonly PluginSettingsFile[];
|
|
11281
11321
|
/**
|
|
11282
|
-
*
|
|
11283
|
-
*
|
|
11284
|
-
* 放在闸门里而不是循环里:`justWarned` 是一次性标记,翻译逻辑跟着标记走
|
|
11285
|
-
* 才不会出现「某条路径忘了发告警」的漂移。
|
|
11322
|
+
* 第 ∞ 层:企业托管设置文件。不给就用 `managedSettingsPath()` 的机器级默认值。
|
|
11286
11323
|
*
|
|
11287
|
-
*
|
|
11288
|
-
*
|
|
11289
|
-
* 可以被 `BudgetStore` 恢复成带着上次会话的余额)。所以由 `loop.ts` 传进来,
|
|
11290
|
-
* 没有就不带这个键。
|
|
11324
|
+
* **这个参数存在的唯一理由是可测性和嵌入宿主**,不是给用户的开关:它没有
|
|
11325
|
+
* 对应的环境变量、也没有对应的命令行参数,理由见 infra 的 `managedSettingsPath()`。
|
|
11291
11326
|
*/
|
|
11292
|
-
|
|
11293
|
-
|
|
11294
|
-
|
|
11295
|
-
|
|
11296
|
-
|
|
11297
|
-
|
|
11298
|
-
|
|
11299
|
-
|
|
11327
|
+
managedPath?: string;
|
|
11328
|
+
}
|
|
11329
|
+
/** 三张列表的总条数 */
|
|
11330
|
+
declare function countRules(lists: PermissionRuleLists | undefined): number;
|
|
11331
|
+
/**
|
|
11332
|
+
* 合并 ③④⑤∞ 四层文件设置到已加载的配置上。
|
|
11333
|
+
*
|
|
11334
|
+
* **会抛**:`opts.settingsPath` 指的文件不是合法 JSON 时抛 `SettingsFileError`
|
|
11335
|
+
* (方案 29 验收 #16,理由见 `finishLayer`)。其余任何一层坏了都不抛。
|
|
11336
|
+
*
|
|
11337
|
+
* @param base `loadConfig()` 的产物(第 ①② 层)。**不会被修改**
|
|
11338
|
+
* @param projectRoot 项目根,取自 `resolveProjectRoot()` —— 必须和信任判定用的是
|
|
11339
|
+
* 同一个答案,否则会出现「诊断说未信任、实际却加载了」
|
|
11340
|
+
* @param execTrusted 工作区授过**执行类**吗(`TrustDecision.exec`),取自
|
|
11341
|
+
* `decideTrust()`。本文件不自己判
|
|
11342
|
+
*
|
|
11343
|
+
* ## ⚠️ ③④ 两层**整份**归执行类,不按字段拆(2026-09-11)
|
|
11344
|
+
*
|
|
11345
|
+
* 拆档之后有一个看着更细的选法:`permissions.allow` 归执行类,`model` /
|
|
11346
|
+
* `maxTurns` 那两个标量跟着读类回来。**不这么做**,判据是这一层的 `gated`
|
|
11347
|
+
* 语义已经是「读到了、也解析了,但只有 deny 被采纳」——再按字段切一刀,
|
|
11348
|
+
* 这一层就有了三种半生不熟的状态,而设置页那条六层来源链要为每一格分别解释
|
|
11349
|
+
* 「这一格为什么在、隔壁那格为什么不在」。
|
|
11350
|
+
*
|
|
11351
|
+
* 代价如实写着:只授了读类的工作区,它 `.epoch/settings.json` 里的 `model`
|
|
11352
|
+
* 不生效 —— 和拆档之前「未信任」那一档的行为**逐字节相同**,不是新增的限制。
|
|
11353
|
+
*/
|
|
11354
|
+
declare function resolveSettings(base: EpochConfig, projectRoot: string, execTrusted: boolean, opts?: ResolveSettingsOptions): SettingsResolution;
|
|
11355
|
+
|
|
11356
|
+
/**
|
|
11357
|
+
* 「为什么是这个值」—— 一个配置键赢在哪一层,以及被压住的层各自写了什么。
|
|
11358
|
+
*
|
|
11359
|
+
* ```
|
|
11360
|
+
* ① 内置默认 schema 的 `.default()` ┐
|
|
11361
|
+
* ② 用户级 ~/.epoch/config.yaml ├ loader 那一相,合成 `base`
|
|
11362
|
+
* 环境变量 EPOCH_* / provider 的 API key 变量 ┘
|
|
11363
|
+
* ③ 项目级 <项目根>/.epoch/settings.json ┐
|
|
11364
|
+
* ④ 项目本地 <项目根>/.epoch/settings.local.json ├ resolveSettings 那一相
|
|
11365
|
+
* ⑤ 命令行 --settings <file> │
|
|
11366
|
+
* ∞ 企业托管 机器级 managed-settings.json ┘
|
|
11367
|
+
* ```
|
|
11368
|
+
*
|
|
11369
|
+
* ## 为什么要单独一相,而不是让 `resolveSettings` 顺手交出来
|
|
11370
|
+
*
|
|
11371
|
+
* `resolveSettings` 拿到的 `base` 已经是 loader 合并完的产物 —— schema 默认值、
|
|
11372
|
+
* `config.yaml`、环境变量全拍在了一起。它分不出 ① 和 ②,而**分不出的代价是
|
|
11373
|
+
* 撒谎**:一个用户从没配过的键会被标成「② 用户级」,于是他去翻一个根本没写
|
|
11374
|
+
* 这一行的文件。所以 ①/② 那一刀由 loader 的 `getConfigOrigins()` 来切,
|
|
11375
|
+
* 本文件只负责把两相拼成一条链。
|
|
11376
|
+
*
|
|
11377
|
+
* ## 三条口径,写在这儿免得下一个人各猜一遍
|
|
11378
|
+
*
|
|
11379
|
+
* 1. **链条只有六格,`plugin`(第 ①ᐟ⁵ 层)不在里面。** 那一层只认
|
|
11380
|
+
* `permissions.deny`,标量键它一个都碰不到(判据在 `loadPluginLayer`)。
|
|
11381
|
+
* 摆一格永远是「—」的行,等于告诉用户「装个插件也许能换掉你的模型」。
|
|
11382
|
+
* 2. **不在白名单里的键压根不产出链条**(`chain: []`)。`SettingsFileSchema`
|
|
11383
|
+
* 只放行 `permissions` / `model` / `maxTurns`,别的键 ③④⑤∞ 碰不到 ——
|
|
11384
|
+
* 给它们画一条六层链条,就是在暗示「这一行也可能被项目级压回去」,
|
|
11385
|
+
* 而用户会照着这个暗示去 `.epoch/settings.json` 里写一个永远不生效的键。
|
|
11386
|
+
* 3. **`unknown` 是留给「分不出」的,不是给「没配过」的。** 没配过是
|
|
11387
|
+
* `builtin`,那是个确切答案。`unknown` 只在「base 里的值和内置默认不一样、
|
|
11388
|
+
* 却没有任何一处记账说是谁写的」时出现 —— 今天它够不到(loader 把
|
|
11389
|
+
* `config.yaml` 和每一个环境变量覆盖都记了账),留着是为了哪天多一条
|
|
11390
|
+
* 没记账的来路时,它显示成一个陌生的词,而不是被悄悄归到「② 用户级」。
|
|
11391
|
+
*/
|
|
11392
|
+
|
|
11393
|
+
/**
|
|
11394
|
+
* 一个配置键的值。
|
|
11395
|
+
*
|
|
11396
|
+
* 收数组是给**有序的链**这种值留的形态(2.0.0 之前就是 `fallback.providers`
|
|
11397
|
+
* 那一行,随老写法删了;今天名单里一个都没有,形态留着)—— **不在这里 join 成
|
|
11398
|
+
* 字符串**:分隔符是排版,两份 catalog 里未必一样,而服务端拼一次就等于把一个
|
|
11399
|
+
* 界面决定钉死在网线上。
|
|
11400
|
+
*/
|
|
11401
|
+
type SettingValue = string | number | boolean | readonly string[];
|
|
11402
|
+
/**
|
|
11403
|
+
* 一个配置项的来源层。编号与叫法逐字照 [docs/SETTINGS.md](../../../../docs/SETTINGS.md)
|
|
11404
|
+
* 那张层表,**不另起一套** —— 用户在文档里读到的「③ 项目级」和界面上看到的
|
|
11405
|
+
* 必须是同一个词。
|
|
11406
|
+
*
|
|
11407
|
+
* `env` 不在那张层表里,因为它不是一个「设置层」:环境变量是在 loader 那一相
|
|
11408
|
+
* 覆盖 `config.yaml` 的,位置和用户级同一格(于是它**会被项目级压回去**,
|
|
11409
|
+
* 这条反直觉的事实正是这条链要说出来的东西之一)。
|
|
11410
|
+
*/
|
|
11411
|
+
type SettingLayer = 'builtin' | 'user' | 'env' | 'project' | 'projectLocal' | 'cli' | 'managed' | 'unknown';
|
|
11412
|
+
/** 链条上的一格。这一层没写这个键时**整个不带 `value`** —— 界面画成「—」 */
|
|
11413
|
+
interface SettingStep {
|
|
11414
|
+
layer: SettingLayer;
|
|
11415
|
+
value?: SettingValue;
|
|
11416
|
+
}
|
|
11417
|
+
interface SettingChain {
|
|
11418
|
+
/** 点分路径,与 `config.yaml` 里的写法逐字一致(`budget.maxCostUsd`) */
|
|
11419
|
+
key: string;
|
|
11420
|
+
/** 生效值。谁都没写、又没有内置默认时**整个不带这个键** */
|
|
11421
|
+
value?: SettingValue;
|
|
11422
|
+
/** 赢的那一层 */
|
|
11423
|
+
layer: SettingLayer;
|
|
11424
|
+
/** 六层链条,低 → 高。不在白名单里的键是**空数组**(判据见文件头第 2 条) */
|
|
11425
|
+
chain: readonly SettingStep[];
|
|
11300
11426
|
}
|
|
11427
|
+
/** loader 那一相里,一个键的值是谁写的 */
|
|
11428
|
+
type BaseOrigin = 'builtin' | 'user' | 'env';
|
|
11429
|
+
/**
|
|
11430
|
+
* 一次 `loadConfig()` 里,哪些键是**用户自己写的**,以及写在哪儿。
|
|
11431
|
+
*
|
|
11432
|
+
* 键是点分路径(`model` / `budget.maxCostUsd`),和 `config.yaml` 里的写法一致 ——
|
|
11433
|
+
* 界面上那一行的标题就是拿它当 catalog key 的后缀,两边对不上会显示成一个空格子。
|
|
11434
|
+
*/
|
|
11435
|
+
interface ConfigOrigins {
|
|
11436
|
+
/** `~/.epoch/config.yaml` 里真的出现过、而且**真的生效了**的键 */
|
|
11437
|
+
readonly file: ReadonlySet<string>;
|
|
11438
|
+
/** 被环境变量覆盖掉的键(`EPOCH_MODEL` / provider 的 API key 变量等) */
|
|
11439
|
+
readonly env: ReadonlySet<string>;
|
|
11440
|
+
}
|
|
11441
|
+
/** 空账。用户级什么都没写时的形状,也是 loader 起不来时的诚实答案 */
|
|
11442
|
+
declare function noConfigOrigins(): ConfigOrigins;
|
|
11443
|
+
/**
|
|
11444
|
+
* 这个键在 loader 那一相是谁写的。
|
|
11445
|
+
*
|
|
11446
|
+
* **环境变量优先于文件**,因为解析链就是这个顺序(loader 第 5 层压第 2 层):
|
|
11447
|
+
* 两处都写了时生效的是环境变量,说成「② 用户级」会让用户去改一个改了也不管用
|
|
11448
|
+
* 的地方。
|
|
11449
|
+
*/
|
|
11450
|
+
declare function originOf(origins: ConfigOrigins, key: string): BaseOrigin;
|
|
11451
|
+
/** loader 那一相交出来的一格 */
|
|
11452
|
+
interface BaseSlot {
|
|
11453
|
+
/** `loadConfig()` 产物里这个键的值 */
|
|
11454
|
+
value?: SettingValue;
|
|
11455
|
+
/** `defaultConfig()` 里这个键的值 —— ① 那一格 */
|
|
11456
|
+
builtin?: SettingValue;
|
|
11457
|
+
/** 谁写的,来自 {@link originOf} */
|
|
11458
|
+
origin: BaseOrigin;
|
|
11459
|
+
}
|
|
11460
|
+
/** 白名单键才给得出的那一半:这个键在 `LayerScalars` 里叫什么、各层结果是什么 */
|
|
11461
|
+
interface FileSlots {
|
|
11462
|
+
key: ScalarKey;
|
|
11463
|
+
layers: readonly LayerOutcome[];
|
|
11464
|
+
}
|
|
11465
|
+
/**
|
|
11466
|
+
* 拼一条链。
|
|
11467
|
+
*
|
|
11468
|
+
* @param key 点分路径,原样带进结果里(本函数不解析它,取值是调用方的事)
|
|
11469
|
+
* @param base loader 那一相的结论
|
|
11470
|
+
* @param file ③④⑤∞ 那一相。**不给 = 这个键不在白名单里**,于是 `chain` 是空数组
|
|
11471
|
+
*
|
|
11472
|
+
* 赢的那一层 = 链条上**最后一个写了值**的格子;一个都没写就是 ①(那时值要么是
|
|
11473
|
+
* 内置默认,要么这个键压根没有默认值 —— 后者的诚实答案仍然是「① 内置默认,
|
|
11474
|
+
* 而它是空的」,不是「不知道」)。
|
|
11475
|
+
*/
|
|
11476
|
+
declare function explainSetting(key: string, base: BaseSlot, file?: FileSlots): SettingChain;
|
|
11301
11477
|
|
|
11302
11478
|
/**
|
|
11303
|
-
*
|
|
11479
|
+
* 配置系统 — 10 层解析链 [Hermes config.py]。
|
|
11304
11480
|
*
|
|
11305
|
-
*
|
|
11481
|
+
* 1. schema 默认值([config/schema.ts](./schema.ts) 是**唯一**真源)
|
|
11482
|
+
* 2. config.yaml(zod 逐字段补默认值,等价于原来的 deep-merge)
|
|
11483
|
+
* 3. ${ENV_VAR} 模板展开(递归,最多 10 层)
|
|
11484
|
+
* 3.5 SecretStore(系统钥匙串,装配层预取后注入 —— 见 `readProviderKeyEnv`)
|
|
11485
|
+
* 4. .env 文件(secrets)
|
|
11486
|
+
* 5. process.env 直接覆盖(最高优先级)—— `EPOCH_BASE_URL` / `EPOCH_PROVIDER` 两个例外:
|
|
11487
|
+
* 它们**什么都不改**,只各报一条诊断(`reportDroppedEnv`)
|
|
11488
|
+
* 6. 损坏回退(last-known-good → 默认值)
|
|
11489
|
+
* 7. 损坏备份(config.yaml.corrupt.<ts>.bak)
|
|
11490
|
+
* 8. 缓存(mtime + size + env_snapshot)
|
|
11491
|
+
* 9. 多 Profile(~/.epoch/profiles/<name>/)
|
|
11492
|
+
* 10. load() / loadReadonly() 双接口
|
|
11306
11493
|
*
|
|
11307
|
-
*
|
|
11308
|
-
*
|
|
11309
|
-
*
|
|
11494
|
+
* 校验:全程 zod(`parseLenient`),**失败不抛异常**——单个配置文件写错不该让
|
|
11495
|
+
* agent 起不来(safeInit 语义)。代价是必须把 issues 交出去:`loadConfig(diags)`
|
|
11496
|
+
* 的可选参数,或事后 `getConfigIssues()`。静默吞掉等于没校验。
|
|
11310
11497
|
*
|
|
11311
|
-
*
|
|
11312
|
-
*
|
|
11313
|
-
* micro-USD(1e-6 美元)对任何现实用量都是整数级精度,累加无损,
|
|
11314
|
-
* 只在对外暴露时除回美元。
|
|
11498
|
+
* 模板展开在校验**之前**做:`maxTurns: ${MAX_TURNS}` 这种写法在展开前是字符串,
|
|
11499
|
+
* 先校验会把合法配置判成类型错误。[Gemini resolveEnvVarsInObject → validateSettings]
|
|
11315
11500
|
*
|
|
11316
|
-
* ##
|
|
11501
|
+
* ## 2.0.0:老写法读到就忽略
|
|
11317
11502
|
*
|
|
11318
|
-
*
|
|
11319
|
-
*
|
|
11320
|
-
*
|
|
11321
|
-
*
|
|
11322
|
-
* SDK 的 `noCacheTokens`),所以 `computeCostMicros` 里那两行缓存乘法
|
|
11323
|
-
* 从这版起是真在算钱,不再恒等于 0。
|
|
11324
|
-
* - `reasoningTokens` **包含在** `outputTokens` 里,**不单独计价、也不加进总数** ——
|
|
11325
|
-
* 加了就是把输出侧算两遍。
|
|
11503
|
+
* `provider:` 段和裸模型名(`model: gpt-4o` / `deepseek/deepseek-chat`)删了,模型只认
|
|
11504
|
+
* 「我的模型」条目(`@<id>`)。旧文件里还有它们时**不报错、不迁移**(用户拍的板:唯一的真实
|
|
11505
|
+
* 用户是星云,它跟着出一版),只是剥掉 + 每处一条 `legacy_ignored` —— 剥在 zod **之前**,
|
|
11506
|
+
* 判据在 {@link stripLegacyModelConfig} 上。
|
|
11326
11507
|
*/
|
|
11327
11508
|
|
|
11509
|
+
/** 加载配置时的覆盖项 */
|
|
11510
|
+
interface LoadConfigOptions {
|
|
11511
|
+
/**
|
|
11512
|
+
* 家目录覆盖 —— 嵌入宿主用(Electron 的 `app.getPath('userData')`)。不给时
|
|
11513
|
+
* (CLI / TUI 的常态)行为与改造前完全一致;给了就**压过**环境
|
|
11514
|
+
* (`EPOCH_HOME` / `EPOCH_PROFILE`)和 yaml 里的 `homeDir` / `dbPath`:这个选项的
|
|
11515
|
+
* 契约是「这个 runtime 实例的数据全落在这里」,让 yaml 能反过来改掉它就会出现
|
|
11516
|
+
* 「嵌入方指定了目录、引擎却写回 `~/.epoch`」。`dbPath` 必须跟着一起换 ——
|
|
11517
|
+
* SessionManager / MemoryManager / TrackerDB 都吃它,只改 homeDir 的话
|
|
11518
|
+
* 两个实例照样共用同一个 SQLite 文件。
|
|
11519
|
+
*/
|
|
11520
|
+
homeDir?: string;
|
|
11521
|
+
}
|
|
11328
11522
|
/**
|
|
11329
|
-
*
|
|
11523
|
+
* 完整加载(深拷贝),适合 CLI 入口。
|
|
11330
11524
|
*
|
|
11331
|
-
*
|
|
11332
|
-
*
|
|
11333
|
-
* 累加一万次也不会像浮点那样越滚越偏。
|
|
11525
|
+
* @param diags 传进来就把校验问题按「一个问题一行」追加进去(宿主的启动诊断)。
|
|
11526
|
+
* 不传也不会静默:问题存在 `getConfigIssues()` 里。
|
|
11334
11527
|
*/
|
|
11335
|
-
declare function
|
|
11528
|
+
declare function loadConfig(diags?: DiagnosticSink, opts?: LoadConfigOptions): EpochConfig;
|
|
11529
|
+
/** 最近一次加载的校验问题(带字段路径),空数组表示配置全合法 */
|
|
11530
|
+
declare function getConfigIssues(): ParseIssue[];
|
|
11336
11531
|
/**
|
|
11337
|
-
*
|
|
11532
|
+
* 最近一次加载里,**哪些键是用户自己写的**(方案 43 §七的来源标注)。
|
|
11533
|
+
*
|
|
11534
|
+
* ## 这个函数存在的理由只有一句话
|
|
11535
|
+
*
|
|
11536
|
+
* `loadConfig()` 交出来的那份配置已经把 ①(schema 默认值)、②(`config.yaml`)
|
|
11537
|
+
* 和环境变量拍在了一起,分不出来。而设置页每一行都要挂来源层徽标 ——
|
|
11538
|
+
* 分不出的话,一个用户从没配过的键会被标成「② 用户级」,于是他去翻一个根本
|
|
11539
|
+
* 没写这一行的文件。**那是这一屏最不该出的一句假话**(判据全文在
|
|
11540
|
+
* [provenance.ts](./provenance.ts) 的文件头)。
|
|
11541
|
+
*
|
|
11542
|
+
* ## 两条口径
|
|
11543
|
+
*
|
|
11544
|
+
* - **`file` 记的是「真的生效了」,不是「文件里写了」。** 整份 zod 校验失败时
|
|
11545
|
+
* loader 是**整体退回默认值**(见 `parseRawConfig` 的注释),那时用户写的一个键
|
|
11546
|
+
* 都没生效,所以 `file` 是空集 —— 不是空集的话,设置页会把一屏默认值挨个标成
|
|
11547
|
+
* 「② 用户级」。未知键同理不记:它们被 `z.object()` 丢掉了。
|
|
11548
|
+
* - **`LoadConfigOptions.homeDir` 覆盖掉的 `homeDir` / `dbPath` 不记账。**
|
|
11549
|
+
* 那是嵌入宿主在装配期定的,既不是 ① 也不是 ②,而这两个键设置页不显示。
|
|
11550
|
+
* 哪天要显示了,它需要的是自己的一档(「宿主」),不是硬塞进现有的三档。
|
|
11551
|
+
*
|
|
11552
|
+
* 配置损坏退到 last-known-good 时,交出来的是**那一份的账**,不是空账 ——
|
|
11553
|
+
* 生效的配置是它,账也该是它的。
|
|
11338
11554
|
*/
|
|
11339
|
-
declare function
|
|
11340
|
-
/** 微美元 → 美元 */
|
|
11341
|
-
declare function microsToUsd(micros: number): number;
|
|
11342
|
-
/** 美元 → 微美元(用于把用户配的 maxCostUsd 转进同一套整数体系) */
|
|
11343
|
-
declare function usdToMicros(usd: number): number;
|
|
11555
|
+
declare function getConfigOrigins(): ConfigOrigins;
|
|
11344
11556
|
/**
|
|
11345
|
-
*
|
|
11557
|
+
* 「我的模型」某个条目自己的 key(2026-10-15)。三层的优先级同 {@link readSecret}。
|
|
11346
11558
|
*
|
|
11347
|
-
*
|
|
11348
|
-
*
|
|
11559
|
+
* 名字由 protocol 的 `entrySecretName(id)` 算出来。没有就是 `undefined` ——
|
|
11560
|
+
* 2.0.0 起**不再退到那家 provider 的环境变量 key**:条目的 key 只认它自己那把。
|
|
11561
|
+
*/
|
|
11562
|
+
declare function readEntryKey(id: string, homeDir?: string): string | undefined;
|
|
11563
|
+
/**
|
|
11564
|
+
* 这台机器**跑得起来吗**:至少有一条能注册进路由的条目。
|
|
11349
11565
|
*
|
|
11350
|
-
*
|
|
11566
|
+
* 两条道:任一条凭据({@link hasProviderCredentials}),或者一条**不吃 key 的条目** ——
|
|
11567
|
+
* 本地 Ollama、自建 OpenAI 兼容端点。后一条是 2026-10-15 补的,判据在 core router 的
|
|
11568
|
+
* `registerEntries` 上(那两类没 key 也注册)。
|
|
11351
11569
|
*
|
|
11352
|
-
*
|
|
11353
|
-
* `
|
|
11354
|
-
*
|
|
11570
|
+
* ⚠️ **它和 `hasProviderCredentials` 是两个问题,别合并**:那条答的是「凭据配了没」
|
|
11571
|
+
* (`epoch status` 那一行「凭证」用它),这条答的是「这台机器现在能不能跑一个模型」。
|
|
11572
|
+
* 一台只挂了本地 Ollama 的机器,前者是 false、后者是 true —— 拿前者当「能不能跑」用,
|
|
11573
|
+
* 那台机器只会收到一句「run epoch model to set a model and API key」然后**什么都不跑**
|
|
11574
|
+
* (2026-10-15 的现场)。
|
|
11575
|
+
*/
|
|
11576
|
+
declare function hasUsableModel(homeDir?: string): boolean;
|
|
11577
|
+
/**
|
|
11578
|
+
* 是否已配置任一条凭据:「我的模型」里任一条的 key(`EPOCH_MODEL_KEY_<id>`)。
|
|
11355
11579
|
*
|
|
11356
|
-
*
|
|
11357
|
-
*
|
|
11358
|
-
* 改名会让调用点看起来像是可以传别的币种进来 —— 那是这里最不想要的误解。
|
|
11580
|
+
* 2.0.0 起不再看各家 provider 的环境变量 key(`DEEPSEEK_API_KEY` 之类)——零配置自动认领
|
|
11581
|
+
* 随老写法删了,那些变量今天不让任何一个模型跑起来,算进来就是假话。
|
|
11359
11582
|
*
|
|
11360
|
-
*
|
|
11361
|
-
*
|
|
11362
|
-
* 常在 $0.003 这一档,两位会印成一个「免费」的假象)
|
|
11583
|
+
* 钥匙串只看**名字**(`listStoredSecretNames()`,纯文件读):这个函数是同步的,而它要回答的
|
|
11584
|
+
* 正是「要不要提示用户去加一条模型」,名字这一层足够。
|
|
11363
11585
|
*/
|
|
11364
|
-
declare function
|
|
11586
|
+
declare function hasProviderCredentials(homeDir?: string): boolean;
|
|
11587
|
+
|
|
11365
11588
|
/**
|
|
11366
|
-
*
|
|
11589
|
+
* 「我的模型」的落盘:`<homeDir>/models.json`(2026-10-15)。
|
|
11367
11590
|
*
|
|
11368
|
-
*
|
|
11369
|
-
*
|
|
11370
|
-
* 这个 provider 支持缓存只是没命中。
|
|
11591
|
+
* 形状、引用写法(`@<id>`)和「为什么单独一份 JSON 而不是 config.yaml 的一节」
|
|
11592
|
+
* 在 protocol 的 `model-entry.ts` 文件头,这里只管读写。
|
|
11371
11593
|
*
|
|
11372
|
-
* ##
|
|
11594
|
+
* ## 坏一条只丢那一条
|
|
11373
11595
|
*
|
|
11374
|
-
*
|
|
11375
|
-
*
|
|
11376
|
-
*
|
|
11377
|
-
*
|
|
11596
|
+
* `config.yaml` 是「一个字段校验不过就整份回退默认值」(loader 的 `parseLenient`)。
|
|
11597
|
+
* 这份文件**不照那个做**:它是一张列表,条目之间互不依赖,而用户手改坏一条时
|
|
11598
|
+
* 把另外五条一起丢掉、主模型跟着退回默认,是一个远比「那一条不见了」更难懂的结局。
|
|
11599
|
+
* 丢掉的那条进 `issues`,装配层把它打进诊断。
|
|
11378
11600
|
*
|
|
11379
|
-
*
|
|
11380
|
-
* 的 usage(OpenAI 兼容端点不带 `stream_options` 的那一家就这样),而同一段会话
|
|
11381
|
-
* 里换 provider(降级、或压缩那一发走别的模型)就会把两种 usage 加到一起。
|
|
11601
|
+
* ## 文件里没有 key
|
|
11382
11602
|
*
|
|
11383
|
-
*
|
|
11603
|
+
* key 进 SecretStore(名字是 `entrySecretName(id)`)。这份文件因此可以被用户
|
|
11604
|
+
* 打开、复制、发给别人看,而不会顺手把凭据带出去。
|
|
11605
|
+
*/
|
|
11606
|
+
|
|
11607
|
+
/** 文件名。和 `config.yaml` 同一个目录 */
|
|
11608
|
+
declare const MODEL_ENTRIES_FILE = "models.json";
|
|
11609
|
+
declare function modelEntriesPath(homeDir: string): string;
|
|
11610
|
+
/**
|
|
11611
|
+
* 把一条**已经校验过**的条目收成规范形状。读和写都过这一道,所以盘上的永远是同一种写法。
|
|
11384
11612
|
*
|
|
11385
|
-
*
|
|
11386
|
-
*
|
|
11387
|
-
*
|
|
11388
|
-
*
|
|
11613
|
+
* - `protocol` / `rawUrl` 只对自定义端点(`openai-compatible`)有意义,别家的丢掉 ——
|
|
11614
|
+
* 留着的话界面上会读出一个「这条走 Anthropic 协议」,而它其实走 DeepSeek 自己的 SDK;
|
|
11615
|
+
* - `rawUrl` 只对 `openai-chat` 有意义(它省掉的正是 `/chat/completions` 那一段);
|
|
11616
|
+
* - 档位去重并按 {@link REASONING_EFFORTS} 的顺序排,滑块从左到右就是这个顺序;
|
|
11617
|
+
* `default` 不在档位里就丢掉,免得默认值指向一个滑块上没有的刻度;
|
|
11618
|
+
* - 空串一律视为没填。
|
|
11619
|
+
*/
|
|
11620
|
+
declare function normalizeModelEntry(entry: ModelEntry): ModelEntry;
|
|
11621
|
+
/** 校验一条外来的(网线上的、用户手写的)条目。不合格返回 null + 原因 */
|
|
11622
|
+
declare function parseModelEntry(raw: unknown): {
|
|
11623
|
+
ok: true;
|
|
11624
|
+
entry: ModelEntry;
|
|
11625
|
+
} | {
|
|
11626
|
+
ok: false;
|
|
11627
|
+
reason: string;
|
|
11628
|
+
};
|
|
11629
|
+
interface LoadedModelEntries {
|
|
11630
|
+
entries: ModelEntry[];
|
|
11631
|
+
/** 被丢掉的条目和原因(坏 JSON / 某一条校验不过 / id 重复),给诊断用 */
|
|
11632
|
+
issues: string[];
|
|
11633
|
+
/** 宿主那一组的显示名(`hostPreset.label` 落在这里,2026-10-15)。没有宿主就是 undefined */
|
|
11634
|
+
hostLabel?: string;
|
|
11635
|
+
}
|
|
11636
|
+
/**
|
|
11637
|
+
* 读 `models.json`。文件不在是常态(没用过这个功能),返回空表、没有 issue。
|
|
11389
11638
|
*
|
|
11390
|
-
*
|
|
11391
|
-
*
|
|
11392
|
-
* `totalUsageTokens()` 自己算出来的和这里能算出来的是同一个数,凭空多一个字段
|
|
11393
|
-
* 只会让「provider 报过」和「我们替它算的」变得分不出来。
|
|
11639
|
+
* 判据见文件头「坏一条只丢那一条」。id 重复时**留第一条**:引用按 id 解析,
|
|
11640
|
+
* 留哪条都会让另一条的引用指错,而第一条是文件里先写的那一条、最可能是用户本来的意思。
|
|
11394
11641
|
*/
|
|
11395
|
-
declare function
|
|
11642
|
+
declare function loadModelEntries(homeDir: string): LoadedModelEntries;
|
|
11396
11643
|
/**
|
|
11397
|
-
*
|
|
11644
|
+
* 整份写回。写 `<path>.<pid>.tmp` 再 rename,形状照 `config/write.ts` 的 `commit()`。
|
|
11398
11645
|
*
|
|
11399
|
-
*
|
|
11400
|
-
*
|
|
11401
|
-
* (见 loop.ts 的注释),而 `estimateTokens()` 只按字符数算,压根看不见图片。
|
|
11402
|
-
* 于是「点一下截图,钱没了」这件事只能事后才发现。
|
|
11646
|
+
* 新建时给 0o600:它和凭据住在同一个 `~/.epoch/` 下,而且里面有用户的自建端点地址
|
|
11647
|
+
* (内网地址本身就是一种不该随手给同机别的用户读的信息)。
|
|
11403
11648
|
*
|
|
11404
|
-
*
|
|
11405
|
-
*
|
|
11406
|
-
*
|
|
11407
|
-
*
|
|
11408
|
-
*
|
|
11649
|
+
* 盘上那份有读不进来的部分(整份坏 / 某几条不合法)时,先原样留一份
|
|
11650
|
+
* `<path>.corrupt.<时间>.bak` 再写:调用方拿到的 `entries` 里本来就没有那几条,
|
|
11651
|
+
* 不留的话「坏一条只丢那一条」这一写就变成真丢了(整份坏时是全丢)。
|
|
11652
|
+
* 形状照 `loader.ts` 的 `backupCorruptConfig`。
|
|
11653
|
+
*
|
|
11654
|
+
* @throws 写不进去时原样抛 —— 调用方(runtime)决定怎么告诉用户
|
|
11409
11655
|
*/
|
|
11410
|
-
declare function estimateImageTokens(width?: number, height?: number): number | undefined;
|
|
11411
11656
|
/**
|
|
11412
|
-
*
|
|
11657
|
+
* 写回。第三个参数是新加的:宿主那一组的显示名(只在宿主预设时传;不传 = 保留盘上的,见下)。
|
|
11413
11658
|
*
|
|
11414
|
-
*
|
|
11415
|
-
*
|
|
11659
|
+
* ⚠️ 用户这一侧(设置页保存)**永远不带 hostLabel** —— 盘上那份是宿主写的,原样保留;
|
|
11660
|
+
* 只有宿主预置(`host-preset.ts`)才动它。
|
|
11416
11661
|
*/
|
|
11417
|
-
declare function
|
|
11662
|
+
declare function saveModelEntries(homeDir: string, entries: readonly ModelEntry[], hostLabel?: string): void;
|
|
11418
11663
|
|
|
11419
11664
|
/**
|
|
11420
|
-
*
|
|
11665
|
+
* 「这个模型认不认图」—— **现问模型本人,不查表**(2026-09-11)。
|
|
11421
11666
|
*
|
|
11422
|
-
* ##
|
|
11667
|
+
* ## 为什么需要它:查表这条路有一个它永远答不出的问题
|
|
11423
11668
|
*
|
|
11424
|
-
*
|
|
11425
|
-
*
|
|
11426
|
-
*
|
|
11427
|
-
*
|
|
11428
|
-
*
|
|
11669
|
+
* `model-metadata.ts` 的三层(静态表 → OpenRouter 缓存 → 默认值)全都是
|
|
11670
|
+
* **别人对这个模型的描述**。上游改一次名字,两层同时失效,第三层的默认值是
|
|
11671
|
+
* `supportsImages: false` —— 于是一个认图的模型在我们眼里瞎掉,而且是**静默**
|
|
11672
|
+
* 瞎掉:用户得到的是「当前模型不支持图片输入」,一句可核对的理由都没有。
|
|
11673
|
+
*
|
|
11674
|
+
* 2026-09-11 实测的现场:DeepSeek 把 `deepseek-v4-flash-vision-exp` 改名成
|
|
11675
|
+
* `deepseek-flash`,静态表 miss、OpenRouter 那份 872 条里**根本没有这个裸名**,
|
|
11676
|
+
* 于是 `epoch -i 图.png` 被我们自己拦死。而同一张图直接发给它,它答得一字不差。
|
|
11677
|
+
*
|
|
11678
|
+
* ## 判据是 **input token 涨没涨**,而这一格换过两次,两次都是被实测逼的
|
|
11679
|
+
*
|
|
11680
|
+
* ### 第一版:靠 warning —— 当天就否掉了
|
|
11681
|
+
*
|
|
11682
|
+
* 最省事的做法是不确定就照发,SDK 报了 warning(`droppedUserContentParts`)
|
|
11683
|
+
* 就说明它不认。**走不通**,同一天的 A/B:
|
|
11684
|
+
*
|
|
11685
|
+
* | 模型 | 出站 body | warnings | 回答 | input token |
|
|
11686
|
+
* | ----------------- | --------- | -------- | ----------- | ----------- |
|
|
11687
|
+
* | `deepseek-flash` | 带图 | `[]` | `"Red"` | 225 |
|
|
11688
|
+
* | `deepseek-v4-pro` | 带图 | `[]` | `"Unknown"` | 99 |
|
|
11689
|
+
*
|
|
11690
|
+
* 第二行就是那个坑:**它把图无声吞了,一条 warning 都不报**,SDK 和 provider
|
|
11691
|
+
* 两侧都认为这次请求很正常。同理「不报错就算认图」「答了话就算认图」也都被这一行
|
|
11692
|
+
* 否掉 —— 它不报错,它也答了话。
|
|
11693
|
+
*
|
|
11694
|
+
* ### 第二版:靠**回答**(发一张纯红图问颜色,答得出红就算)—— 2026-09-12 否掉
|
|
11695
|
+
*
|
|
11696
|
+
* 它把断言落在了模型的**识图准确率**上,而那个东西既不稳定、也不是我们的责任。
|
|
11697
|
+
* 同一张 132 字节的红方块、同一个型号(`deepseek-v4-flash-vision-exp`)、
|
|
11698
|
+
* **逐字节相同的出站 body**(钩 `fetch` 抓的,四次都是 `bodyLen=50948`),
|
|
11699
|
+
* 连着跑四次的回答是:
|
|
11700
|
+
*
|
|
11701
|
+
* | run | 回答 |
|
|
11702
|
+
* | --- | --------- |
|
|
11703
|
+
* | 1 | `Red` |
|
|
11704
|
+
* | 2 | `White` |
|
|
11705
|
+
* | 3 | `Magenta` |
|
|
11706
|
+
* | 4 | `Blue` |
|
|
11707
|
+
*
|
|
11708
|
+
* 也就是说这一版探针在这个型号上有约 3/4 的概率把一个**确实收到了图**的模型
|
|
11709
|
+
* 判成瞎子,然后把 `false` 写进缓存。
|
|
11429
11710
|
*
|
|
11430
|
-
*
|
|
11711
|
+
* ### 这一版:靠 input token 的**增量**
|
|
11431
11712
|
*
|
|
11432
|
-
*
|
|
11433
|
-
*
|
|
11434
|
-
*
|
|
11435
|
-
* 有一条「两边行为逐项相等」的门禁兜着,这一份不值得再开一条。
|
|
11713
|
+
* 同一批运行里,回答四选一,而 `inputTokens` 纹丝不动 ——
|
|
11714
|
+
* 带图 21088、不带图 20904(各跑两次,两两分毫不差)。差的那 184 个 token
|
|
11715
|
+
* 就是那张图:**模型为它计了费,也就意味着它真的收下并处理了。**
|
|
11436
11716
|
*
|
|
11437
|
-
*
|
|
11438
|
-
* `t()` 逐字一样 —— 判据在 [tui/src/money.ts](../../../tui/src/money.ts)。
|
|
11717
|
+
* 于是探针发两发(同一句话,一发带图一发不带),比 `inputTokens`:
|
|
11439
11718
|
*
|
|
11440
|
-
*
|
|
11719
|
+
* - 涨了 → 图被接收(上表第一行,225 vs 99 也正是这个形状);
|
|
11720
|
+
* - 没涨 → 被无声吞了(上表第二行);
|
|
11721
|
+
* - 拿不到 usage → `inconclusive`,**不写缓存**。
|
|
11441
11722
|
*
|
|
11442
|
-
*
|
|
11443
|
-
*
|
|
11444
|
-
*
|
|
11723
|
+
* ⚠️ **这一版探不出「收了图但看不懂」**,这是刻意放弃的:那是模型质量,
|
|
11724
|
+
* 不在我们的责任边界内。上面那四次 `White` / `Magenta` / `Blue` 在新判据下
|
|
11725
|
+
* 全部算「认图」—— 而它们确实都认图,这正是想要的。
|
|
11445
11726
|
*
|
|
11446
|
-
* ⚠️
|
|
11447
|
-
*
|
|
11448
|
-
*
|
|
11449
|
-
*/
|
|
11450
|
-
|
|
11451
|
-
/**
|
|
11452
|
-
* 钉住这个进程的展示币种。装配层读完配置后调一次。
|
|
11727
|
+
* ⚠️ 探针的两发**不共享长前缀、也够不着任何 prompt 缓存**(裸 `generateText`,
|
|
11728
|
+
* 没有 system prompt,整条消息一百多 token)。这一条是承重的:provider 一旦
|
|
11729
|
+
* 把 `inputTokens` 按「未命中缓存的部分」报,两发的数就不可比了。
|
|
11453
11730
|
*
|
|
11454
|
-
*
|
|
11455
|
-
* 和 `setLang()` 在同两个入口、同一个位置 —— 理由也一样:它必须跑在**任何一句
|
|
11456
|
-
* 带金额的文案之前**,而最早那几句是启动诊断里的预算行(`boot.budget_cost`)。
|
|
11731
|
+
* ## ⚠️ 第三档 `inconclusive` 的含义跟着换了
|
|
11457
11732
|
*
|
|
11458
|
-
*
|
|
11459
|
-
*
|
|
11733
|
+
* 第二版里它指「正文是空的」(推理型号把输出预算花在思考上)。这一版**不看正文**,
|
|
11734
|
+
* 所以那一档现在指「**拿不到 usage**」(provider 不报、或基线那一发自己失败了)。
|
|
11735
|
+
* 处置没变:这一次按「不认图」走,但**一个字都不写缓存** —— 写进去会把一个
|
|
11736
|
+
* 认图的模型钉死(`true` 的缓存是 30 天)。
|
|
11460
11737
|
*/
|
|
11461
|
-
|
|
11738
|
+
|
|
11462
11739
|
/**
|
|
11463
|
-
*
|
|
11464
|
-
*
|
|
11465
|
-
*
|
|
11466
|
-
*
|
|
11467
|
-
* `t()`」那条,失败形态一模一样:不报错,只是永远印美元。
|
|
11740
|
+
* ⚠️ 2026-09-12 从 1 提到 2:判据从「答不答得出红」换成「input token 涨没涨」
|
|
11741
|
+
* (判据全文在文件头)。**旧结论是用另一把尺子量的**,其中的 `false` 有相当一部分
|
|
11742
|
+
* 是第二版那个 3/4 误判率产出的 —— 继续信它等于把一次已经作废的实验的结果
|
|
11743
|
+
* 当成这次的。整份丢掉,重新探。
|
|
11468
11744
|
*/
|
|
11469
|
-
declare
|
|
11745
|
+
declare const PROBE_VERSION = 2;
|
|
11470
11746
|
|
|
11471
11747
|
/**
|
|
11472
|
-
*
|
|
11748
|
+
* Provider 错误的分类:一个异常对象 → 「该怎么恢复」。
|
|
11473
11749
|
*
|
|
11474
|
-
* 从
|
|
11475
|
-
*
|
|
11476
|
-
*
|
|
11477
|
-
*
|
|
11750
|
+
* 从 `router.ts` 拆出来(方案 26 PR-2)。理由不是行数,是**它是纯函数**:
|
|
11751
|
+
* 输入一个错误、输出一档恢复动作,不碰状态、不碰网络。留在 router 里的时候,
|
|
11752
|
+
* 想验「'maximum context length is 128500 tokens' 会不会被当成 500 服务器错误」
|
|
11753
|
+
* 得先造一个有凭据的 router 出来 —— 而那跟这个判断毫无关系。
|
|
11478
11754
|
*/
|
|
11479
|
-
|
|
11755
|
+
/** 恢复动作。降级链的每一档由它决定走到哪 */
|
|
11756
|
+
type RecoveryAction = 'retry' | 'rotate_credential' | 'switch_model' | 'switch_provider' | 'abort';
|
|
11757
|
+
declare class ProviderError extends Error {
|
|
11758
|
+
readonly code: number;
|
|
11759
|
+
readonly recoverable: boolean;
|
|
11760
|
+
readonly recovery: RecoveryAction;
|
|
11761
|
+
constructor(message: string, code: number, recoverable: boolean, recovery?: RecoveryAction);
|
|
11762
|
+
}
|
|
11480
11763
|
/**
|
|
11481
|
-
*
|
|
11482
|
-
*
|
|
11483
|
-
* 字段从 `EpochConfigSchema` **摘**出来而不是另写一份:值域约束(`maxTurns`
|
|
11484
|
-
* 是正整数、`permissions` 三张列表都是非空字符串)只能有一处定义,
|
|
11485
|
-
* 抄第二份迟早对不上。
|
|
11764
|
+
* 「没有可用凭证」的错误码。
|
|
11486
11765
|
*
|
|
11487
|
-
*
|
|
11766
|
+
* 单独提成常量是因为**装配层要按它分档**:没配 key 是「还没配置」(诊断记
|
|
11767
|
+
* `skipped`),不是「模块坏了」(`failed`)—— 后者会让 `epoch doctor` 退非 0,
|
|
11768
|
+
* 于是一台刚装完、还没配 key 的机器(以及 CI)会被判成坏的。
|
|
11769
|
+
* 见 runtime/src/services.ts 的 Provider 那一段。
|
|
11770
|
+
*/
|
|
11771
|
+
declare const PROVIDER_NO_CREDENTIAL = 1001;
|
|
11772
|
+
/**
|
|
11773
|
+
* 错误分类。
|
|
11488
11774
|
*
|
|
11489
|
-
*
|
|
11490
|
-
*
|
|
11491
|
-
*
|
|
11492
|
-
*
|
|
11493
|
-
* `settings.json` 会顺手把用户配的 model 打回内置默认值 —— 而且诊断里
|
|
11494
|
-
* 一个字都不会说。所以必须先 `.unwrap()` 摘掉 default 再 `.optional()`。
|
|
11775
|
+
* 顺序很关键,而且**语义模式必须排在裸数字之前**:
|
|
11776
|
+
* 原来 `/500|502|503/` 排在 context-length 检查前面,任何含 "500" 的消息
|
|
11777
|
+
* ("maximum context length is 128500 tokens"、"reduce to 500 tokens")
|
|
11778
|
+
* 都会被当成服务器错误去重试——而 context 超限重试一万次也不会好。
|
|
11495
11779
|
*
|
|
11496
|
-
*
|
|
11780
|
+
* 裸状态码只在错误对象本身带 status 时才可靠,所以优先读 status,
|
|
11781
|
+
* 再退化到从消息里找**带边界**的状态码。
|
|
11497
11782
|
*/
|
|
11498
|
-
declare
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11502
|
-
|
|
11503
|
-
}, z.core.$strip>>;
|
|
11504
|
-
model: z.ZodOptional<z.ZodString>;
|
|
11505
|
-
maxTurns: z.ZodOptional<z.ZodInt>;
|
|
11506
|
-
}, z.core.$strip>;
|
|
11507
|
-
type SettingsFile = z.infer<typeof SettingsFileSchema>;
|
|
11783
|
+
declare function classifyProviderError(err: unknown): {
|
|
11784
|
+
code: number;
|
|
11785
|
+
recovery: RecoveryAction;
|
|
11786
|
+
};
|
|
11787
|
+
|
|
11508
11788
|
/**
|
|
11509
|
-
*
|
|
11789
|
+
* 换模型的前置判定(方案 26)。
|
|
11510
11790
|
*
|
|
11511
|
-
*
|
|
11512
|
-
* 显式给的那份文件坏了,CLI 要按「预期内失败」打一行(见 cli 的 `errors.ts`)。
|
|
11791
|
+
* 单独一个文件而不是塞进 `router.ts`:
|
|
11513
11792
|
*
|
|
11514
|
-
*
|
|
11515
|
-
*
|
|
11793
|
+
* 1. `router.ts` 已经 700 多行(AGENTS.md 铁律 3 的上限是 500),不该再长
|
|
11794
|
+
* 2. 这三道检查是**纯函数** —— 给它「有没有凭据」和「模型什么能力」,
|
|
11795
|
+
* 它就能算出换不换得成。抽出来之后单测不用造一个 `ProviderRouter`
|
|
11796
|
+
* (那要真凭据、要碰 `readProviderKeyEnv()`),直接喂假事实就行
|
|
11797
|
+
*
|
|
11798
|
+
* 判定留在 core 而不是 UI 层:换一个宿主(TUI / web / headless)也得是同一套
|
|
11799
|
+
* 判据,放 UI 里就会有第二份、然后两份开始不一致 —— 这个仓库在
|
|
11800
|
+
* 「provider → 环境变量名」那张表上已经吃过一次这个亏。
|
|
11516
11801
|
*/
|
|
11517
|
-
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
|
|
11802
|
+
|
|
11803
|
+
/** 判定要用到的外部事实,由 router 注入 */
|
|
11804
|
+
interface SelectionDeps {
|
|
11805
|
+
/** 这个 provider 有没有可用凭据 */
|
|
11806
|
+
hasCredential: (provider: ProviderType) => boolean;
|
|
11807
|
+
/** 这个模型的能力(窗口 / 视觉 / 工具) */
|
|
11808
|
+
capability: (model: string) => ModelCapability;
|
|
11521
11809
|
}
|
|
11522
11810
|
/**
|
|
11523
|
-
*
|
|
11811
|
+
* 换模型的三道前置检查。
|
|
11524
11812
|
*
|
|
11525
|
-
*
|
|
11526
|
-
*
|
|
11527
|
-
* 位置。所以按 position 自己数一遍换行 —— 「第几行」是用户唯一能拿去改文件的
|
|
11528
|
-
* 信息,而 position 是个要他打开编辑器去数字节的数。
|
|
11813
|
+
* 两条拒绝都是「换过去下一轮必然失败」,**在这里拦住比让 provider 报 400 好**:
|
|
11814
|
+
* 400 是已经计费的,而且错误消息里不会写「因为你刚才换了模型」。
|
|
11529
11815
|
*
|
|
11530
|
-
*
|
|
11816
|
+
* 换模型**不清空上下文**——那正是它的意义所在(对着同一段对话换个更强或更便宜
|
|
11817
|
+
* 的脑子)。代价就是新模型必须接得住已经积累的东西,所以才有这三道检查。
|
|
11531
11818
|
*/
|
|
11532
|
-
declare function
|
|
11819
|
+
declare function evaluateSelection(want: ModelSelection, deps: SelectionDeps, ctx?: SelectionContext): SetSelectionResult;
|
|
11533
11820
|
|
|
11534
11821
|
/**
|
|
11535
|
-
*
|
|
11536
|
-
*
|
|
11537
|
-
* 由 IT 管理员分发到一个**普通用户写不了**的机器级路径
|
|
11538
|
-
* (见 infra 的 `managedSettingsPath()`),压过其余全部五层,且额外带三个
|
|
11539
|
-
* **元开关** —— 它们管的不是「配了什么」,而是「别的层还算不算数」:
|
|
11540
|
-
*
|
|
11541
|
-
* | 开关 | 作用 |
|
|
11542
|
-
* | -------------------------------- | ------------------------------------------ |
|
|
11543
|
-
* | `allowManagedPermissionRulesOnly` | 其余各层的 allow / ask / deny 全部忽略 |
|
|
11544
|
-
* | `allowManagedHooksOnly` | 用户级 hook 一律不加载 |
|
|
11545
|
-
* | `disableBypassPermissionsMode` | `permission: bypass` 拒绝启动 |
|
|
11546
|
-
*
|
|
11547
|
-
* ## 这一层**不过信任闸门**,而且不该过
|
|
11822
|
+
* 模型级降级:降到哪儿、以及什么时候不该再降回来(方案 26 PR-2,验收 #8/#10/#11)。
|
|
11548
11823
|
*
|
|
11549
|
-
*
|
|
11550
|
-
* 管理员 —— 拿仓库信任去闸它没有意义,而且方向是反的:托管策略的典型内容是
|
|
11551
|
-
* deny,越是不信任的目录越需要它生效。
|
|
11824
|
+
* 拆出来而不是继续堆在 `router.ts` 里,有两个理由:
|
|
11552
11825
|
*
|
|
11553
|
-
*
|
|
11826
|
+
* 1. `router.ts` 已经 800 行,铁律 3 的线早就压在上面了
|
|
11827
|
+
* 2. 这两件事**都是纯状态**,不碰 SDK、不碰网络。单独放能被用例直接盯,
|
|
11828
|
+
* 不用为了验「连续失败三次会怎样」去 mock 一整个 AI SDK
|
|
11554
11829
|
*
|
|
11555
|
-
*
|
|
11556
|
-
*
|
|
11557
|
-
*
|
|
11558
|
-
*
|
|
11830
|
+
* ⚠️ **2.0.0 起降级只剩一级**:主模型 → `fallback.model` 指向的那一条(`@<id>`,在 router)→ 失败。
|
|
11831
|
+
* 原来这里还有「同家内置默认模型表」(`DEFAULT_MODELS` / `nextFallbackModel` / `defaultModelOf`)
|
|
11832
|
+
* 和 `fallback.providers`(换一家用那家的默认模型)两级,都是老写法的产物,随它删了 ——
|
|
11833
|
+
* 判据在 router 的 `escalate` 上。这个文件今天只剩节流那一半。
|
|
11559
11834
|
*/
|
|
11560
11835
|
|
|
11561
11836
|
/**
|
|
11562
|
-
*
|
|
11837
|
+
* 连续失败到几次就放弃这个模型(方案 26 验收 #8)。
|
|
11563
11838
|
*
|
|
11564
|
-
*
|
|
11565
|
-
*
|
|
11839
|
+
* 3 而不是 1:偶发 429 / 503 是常态,一次就放弃等于「抖一下就永久换模型」,
|
|
11840
|
+
* 用户明明选了强模型却在跑弱的。也不是 10:每一次重试都是真金白银的一次请求,
|
|
11841
|
+
* 而一个真的挂了的模型不会在第 7 次突然好。
|
|
11566
11842
|
*/
|
|
11567
|
-
declare const
|
|
11568
|
-
permissions: z.ZodOptional<z.ZodObject<{
|
|
11569
|
-
allow: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11570
|
-
ask: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11571
|
-
deny: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11572
|
-
}, z.core.$strip>>;
|
|
11573
|
-
model: z.ZodOptional<z.ZodString>;
|
|
11574
|
-
maxTurns: z.ZodOptional<z.ZodInt>;
|
|
11575
|
-
allowManagedPermissionRulesOnly: z.ZodOptional<z.ZodBoolean>;
|
|
11576
|
-
allowManagedHooksOnly: z.ZodOptional<z.ZodBoolean>;
|
|
11577
|
-
disableBypassPermissionsMode: z.ZodOptional<z.ZodBoolean>;
|
|
11578
|
-
}, z.core.$strip>;
|
|
11579
|
-
type ManagedSettings = z.infer<typeof ManagedSettingsSchema>;
|
|
11843
|
+
declare const MODEL_GIVE_UP_THRESHOLD = 3;
|
|
11580
11844
|
/**
|
|
11581
|
-
*
|
|
11845
|
+
* 模型失败的节流状态(方案 26 验收 #8)。
|
|
11582
11846
|
*
|
|
11583
|
-
*
|
|
11584
|
-
*
|
|
11585
|
-
*
|
|
11847
|
+
* 解决的是这个形状:主模型挂了,每一轮都先拿它试一次、失败、再降级 ——
|
|
11848
|
+
* 于是**每一轮都白烧一次失败请求**,还平白多等一次超时。连续失败到阈值之后
|
|
11849
|
+
* 就不再自动打它,直接从降级链起步。
|
|
11850
|
+
*
|
|
11851
|
+
* ## 键是**路由键**(`provider:entry`),不是 `provider/model`
|
|
11852
|
+
*
|
|
11853
|
+
* 2026-10-16 修的(跨轮节流按条目化改造时漏掉的那一把尺):两个条目可以是**同一家
|
|
11854
|
+
* 同一个模型名、不同端点**(官方 + 中转站,「我的模型」明说要支持的用法)—— 按
|
|
11855
|
+
* `provider/model` 记,这两条共用一本账,于是:
|
|
11856
|
+
*
|
|
11857
|
+
* - 主条目(中转,恒挂)攒的失败,被降级条目(官方,好的)每轮的成功**一笔清零**,
|
|
11858
|
+
* 阈值永远跨不过去 —— 「连续失败 3 次就不再自动用它」这条通知一次都不会出现,
|
|
11859
|
+
* 每一轮都先在坏端点上空烧满重试预算再降级;
|
|
11860
|
+
* - 反过来 `avoidAbandoned` 会把**好的**降级条目也当成「放弃过」,主条目挂掉之后
|
|
11861
|
+
* 不从它起步(表现是等一个多余的超时)。
|
|
11862
|
+
*
|
|
11863
|
+
* 键的口径和 `router.ts` 的 `entryTried`(`@<id>`)是同一条判据:**条目**才是用户
|
|
11864
|
+
* 配置的那个东西,同家同名但不同端点的两条是两种命运。跨家同名(azure / openai 的
|
|
11865
|
+
* `gpt-4o-mini`)不在这个键里区分 —— 它们在路由键里天然就不同(entry 不同)。
|
|
11866
|
+
*
|
|
11867
|
+
* ## 计数单位是「请求」不是「重试」
|
|
11868
|
+
*
|
|
11869
|
+
* 一次 `generate()` 内部为同一个模型重试三次,算 **1** 次失败而不是 3 次。
|
|
11870
|
+
* 重试是同一次意图的一部分;把它算成三次,等于第一轮就把模型判死刑。
|
|
11871
|
+
*
|
|
11872
|
+
* ## 只在本会话内有效,且用户能一句话推翻
|
|
11873
|
+
*
|
|
11874
|
+
* 不落盘:模型挂了通常是分钟级的事,把「这个模型不好使」记到下次启动
|
|
11875
|
+
* 会变成一个没人知道怎么解除的诅咒。`/model` 手动切回来会调 {@link clear},
|
|
11876
|
+
* 那是显式的「我要再试一次」——用户的话永远压过引擎的推断。
|
|
11877
|
+
*
|
|
11878
|
+
* ⚠️ 这个类**不认识 provider / model / 条目**,只收发不透明字符串:键怎么算由
|
|
11879
|
+
* router 一处决定(`routeOf`),两边各算一次就又会长出第二把尺。
|
|
11586
11880
|
*/
|
|
11587
|
-
|
|
11588
|
-
|
|
11589
|
-
|
|
11590
|
-
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11881
|
+
declare class ModelFailureTracker {
|
|
11882
|
+
private readonly counts;
|
|
11883
|
+
private readonly abandoned;
|
|
11884
|
+
/**
|
|
11885
|
+
* 记一次失败。
|
|
11886
|
+
*
|
|
11887
|
+
* @param key 路由键(`routeKey`:`provider:entry`)
|
|
11888
|
+
* @returns 这一下是不是**刚好**跨过阈值。只在跨过的那一次回 `true`,
|
|
11889
|
+
* 调用方据此发一条通知 —— 之后每次失败都发就成了刷屏
|
|
11890
|
+
*/
|
|
11891
|
+
recordFailure(key: string): boolean;
|
|
11892
|
+
/**
|
|
11893
|
+
* 记一次成功 —— 连续计数**清零**,不是减一。
|
|
11894
|
+
*
|
|
11895
|
+
* 「连续」是这里的关键词:跑通了十轮之后再失败一次,和连着失败三次,
|
|
11896
|
+
* 是完全不同的两件事,只有后者说明模型真的不好使了。
|
|
11897
|
+
*/
|
|
11898
|
+
recordSuccess(key: string): void;
|
|
11899
|
+
/** 这个落点已经被放弃了吗 */
|
|
11900
|
+
isAbandoned(key: string): boolean;
|
|
11901
|
+
/** 抹掉一个落点的全部记录。用户手动切回它时调 —— 显式意图压过引擎的推断 */
|
|
11902
|
+
clear(key: string): void;
|
|
11594
11903
|
}
|
|
11595
|
-
|
|
11596
|
-
declare function noManagedPolicy(path: string): ManagedPolicy;
|
|
11904
|
+
|
|
11597
11905
|
/**
|
|
11598
|
-
*
|
|
11906
|
+
* Token 估算 —— 真分词,不再是 `chars / 4`(方案 42 §七「运行前 token 估算」)。
|
|
11907
|
+
*
|
|
11908
|
+
* ## 先量的那一遍
|
|
11599
11909
|
*
|
|
11600
|
-
*
|
|
11601
|
-
*
|
|
11910
|
+
* 改造前全仓统一走 `Math.ceil(text.length / 4)`。拿**本仓库的真实文件**过一遍
|
|
11911
|
+
* (2026-08-15,对照 `o200k_base` 的真实分词结果):
|
|
11602
11912
|
*
|
|
11603
|
-
*
|
|
11604
|
-
*
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
*
|
|
11913
|
+
* | 样本 | chars/4 | 真实 | 真 / 估 |
|
|
11914
|
+
* | ----------------------------------- | ------- | ---- | --------- |
|
|
11915
|
+
* | AGENTS.md 里纯中文的行 | 229 | 616 | **2.69×** |
|
|
11916
|
+
* | AGENTS.md 全文(中英混排散文) | 3008 | 6035 | **2.01×** |
|
|
11917
|
+
* | core/README.md(中英混排) | 2392 | 3333 | 1.39× |
|
|
11918
|
+
* | 三个 `.ts`(代码 + 中文 JSDoc) | — | — | 1.29–1.45× |
|
|
11919
|
+
* | compressor.ts 里纯 ASCII 的行 | 2741 | 2701 | 0.99× |
|
|
11920
|
+
* | 纯英文散文 | 107 | 68 | 0.64× |
|
|
11609
11921
|
*
|
|
11610
|
-
*
|
|
11611
|
-
*
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
readonly code = "MANAGED_POLICY";
|
|
11615
|
-
/** 用户接下来该干什么。和 message 分开,宿主可以排在第二行 */
|
|
11616
|
-
readonly hint: string;
|
|
11617
|
-
constructor(message: string, hint: string);
|
|
11618
|
-
}
|
|
11619
|
-
/**
|
|
11620
|
-
* `disableBypassPermissionsMode` 的执行点(验收 #20)。
|
|
11922
|
+
* 读法是:**英文和纯代码场景它一直是准的,中文场景系统性低估 2–2.7 倍**
|
|
11923
|
+
* (换成 `cl100k_base` 那种老编码更差,纯中文到 3.66×)。而 `/context` 那张
|
|
11924
|
+
* 占用表、路由的「这一轮塞不塞得下」、压缩器的尾部预算全都吃这个数 ——
|
|
11925
|
+
* 所以它不是显示精度问题,是三处功能的地基。
|
|
11621
11926
|
*
|
|
11622
|
-
* ##
|
|
11927
|
+
* ## 为什么挑 gpt-tokenizer
|
|
11623
11928
|
*
|
|
11624
|
-
*
|
|
11625
|
-
*
|
|
11626
|
-
* 权限级别是用户**显式**要的东西(`--permission bypass` 或 config.yaml 里那一行),
|
|
11627
|
-
* 显式请求被策略挡下必须当面说,这跟「配了个不认识的级别回退到 default」
|
|
11628
|
-
* 那种笔误不是一回事。
|
|
11929
|
+
* 三个候选实测(Windows / Node 24,各起**新进程**取 5 次中位数,
|
|
11930
|
+
* 量的是「从 import 到算出第一个数」)。判据按这个顺序:
|
|
11629
11931
|
*
|
|
11630
|
-
*
|
|
11631
|
-
*
|
|
11632
|
-
*
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
*
|
|
11932
|
+
* 1. **不能拖慢冷启动** —— TUI 和 CLI 每次都要起。
|
|
11933
|
+
* `gpt-tokenizer/encoding/o200k_base` **89 ms**,
|
|
11934
|
+
* `js-tiktoken/lite` + `ranks/o200k_base` **289 ms**,3.2 倍差距。
|
|
11935
|
+
* 而且 gpt-tokenizer 按编码分入口,另外五套词表一个字节都不会被解析。
|
|
11936
|
+
* 再加上下面的懒加载,进程启动本身**一毫秒都不多花**
|
|
11937
|
+
* 2. **不能引入 wasm / native 附件** —— `@dqbd/tiktoken` 是 wasm,直接出局:
|
|
11938
|
+
* `pnpm pack` / `verify:pack` 那条路会变复杂,Windows 上装 native 附件
|
|
11939
|
+
* 也是本仓库的已知坑。gpt-tokenizer 是纯 JS、**零运行时依赖**
|
|
11940
|
+
* 3. **体积** —— 这一条 gpt-tokenizer 是**输**的:装完 54 MB,js-tiktoken 22 MB。
|
|
11941
|
+
* 但真正被 require 进内存的只有 `esm/encoding/o200k_base.js` 那 2.3 MB,
|
|
11942
|
+
* 而判据顺序里冷启动排在体积前面,所以认这个亏
|
|
11943
|
+
* 4. **许可证** —— MIT。**只是依赖它,没有抄任何一行代码**,所以本文件
|
|
11944
|
+
* 不加 `SPDX-License-Identifier` 头(AGENTS.md 铁律 12:那个头在本仓库的
|
|
11945
|
+
* 含义是「抄自 gemini-cli」,加了就必须同步登记进 NOTICE)
|
|
11638
11946
|
*
|
|
11639
|
-
*
|
|
11640
|
-
* ① 内置默认 schema 默认值 ┐
|
|
11641
|
-
* ①ᐟ⁵ 插件 ~/.epoch/plugins/<插件>/settings.json ← **只有 deny 生效**(方案 32)
|
|
11642
|
-
* ② 用户级 ~/.epoch/config.yaml ┘ 这两层由 loader.ts 交出来,是本文件的入参 base
|
|
11643
|
-
* ③ 项目级 <项目根>/.epoch/settings.json ← 信任闸门
|
|
11644
|
-
* ④ 项目本地 <项目根>/.epoch/settings.local.json ← 信任闸门
|
|
11645
|
-
* ⑤ 命令行 --settings <file>
|
|
11646
|
-
* ∞ 企业托管 机器级 managed-settings.json ← 不过闸门,见 managed.ts
|
|
11647
|
-
* ```
|
|
11947
|
+
* ## 一套编码走天下,这是**有意的近似**
|
|
11648
11948
|
*
|
|
11649
|
-
*
|
|
11949
|
+
* 模型不同分词器就不同(GPT 的 o200k / cl100k、Claude 自己的 BPE、DeepSeek
|
|
11950
|
+
* 自己的),但今天**所有模型都走 `o200k_base` 这一套**。挑它是因为它是当代
|
|
11951
|
+
* 编码里 CJK 合并最充分的,拿来当 Claude / DeepSeek 的代理比 `cl100k_base` 近。
|
|
11952
|
+
* 真按模型分套要各家的词表文件,那是另一轮的事 —— 这一轮只把口径统一到一处。
|
|
11650
11953
|
*
|
|
11651
|
-
*
|
|
11652
|
-
*
|
|
11653
|
-
* - 其余键**覆盖**,高层级赢
|
|
11954
|
+
* 接口上留了 `model` 形参,今天走到 {@link encodingFor} 就被忽略掉;
|
|
11955
|
+
* 留着是为了将来加分支时**调用方一行都不用改**。
|
|
11654
11956
|
*
|
|
11655
|
-
*
|
|
11957
|
+
* ⚠️ **它仍然是估算。** 真值以 provider 回的 usage 为准(那本来就是准的,
|
|
11958
|
+
* 见 `cost/pricing.ts` 的 `promptTokens`)。别拿这个数去断言「等于 usage」。
|
|
11656
11959
|
*
|
|
11657
|
-
*
|
|
11658
|
-
* `config.trust.enabled` —— 两者都得先把用户级配置解析完才有。塞进 `loadConfig()`
|
|
11659
|
-
* 就成了「加载配置需要先加载配置」。所以做成**加载后的第二相**:
|
|
11660
|
-
* 装配层拿到 base config、判完信任,再调这里。
|
|
11960
|
+
* ## 2026-08-15:换了真分词器**不等于**可以摘掉界面上那枚「估算」徽标
|
|
11661
11961
|
*
|
|
11662
|
-
*
|
|
11962
|
+
* 这一条写在这儿是因为它已经被读反过一次。`design/web-ui/index.html:744` 那条
|
|
11963
|
+
* HTML 注释末尾写着「要拿掉这个徽标得先换真 tokenizer」—— 它是**必要条件**,
|
|
11964
|
+
* 不是充分条件,而同一份稿子的 `thread.css` 里 `.est` 那条写死了充分条件:
|
|
11965
|
+
* 「token 有三个时刻,只有**运行前那一个**是猜的,所以这个徽标只准出现在
|
|
11966
|
+
* `.mhead__sub` 里」。
|
|
11663
11967
|
*
|
|
11664
|
-
*
|
|
11968
|
+
* 换句话说:这个文件治好的是**偏差有多大**(中文场景 2–2.7 倍的系统性低估),
|
|
11969
|
+
* 治不好的是**它是不是猜的**。后者由两件事钉死,两件都还在:
|
|
11665
11970
|
*
|
|
11666
|
-
*
|
|
11667
|
-
*
|
|
11668
|
-
*
|
|
11669
|
-
*
|
|
11670
|
-
* 一个「配了但不生效」的键
|
|
11671
|
-
* - **`hooks` / `provider`** —— hook 会 spawn 子进程,等价于任意代码执行;
|
|
11672
|
-
* provider 里有凭据。这两个哪怕已信任也不该由仓库说了算
|
|
11673
|
-
* - **`compression`(2026-08-16 加这一段时判的)** —— 见下面那一节
|
|
11971
|
+
* 1. 上面「一套编码走天下」那一段 —— 编码是 o200k_base,模型可能是
|
|
11972
|
+
* Claude / DeepSeek,各家 BPE 不同;
|
|
11973
|
+
* 2. 更硬的一条:运行前估算算的是**发之前**的载荷,而这一轮跑起来之后还会
|
|
11974
|
+
* 插进压缩、后台任务通知、工具结果。
|
|
11674
11975
|
*
|
|
11675
|
-
*
|
|
11976
|
+
* 运行前那个数的消费方是 `agent/run-estimate.ts`(决定 8 的第一个时刻),
|
|
11977
|
+
* 判据全文在那个文件头。
|
|
11676
11978
|
*
|
|
11677
|
-
* ##
|
|
11979
|
+
* ## ✅ 「我够不着,你来收」—— **收了**(2026-08-15,`wt/1` 留,合并时结)
|
|
11678
11980
|
*
|
|
11679
|
-
*
|
|
11680
|
-
*
|
|
11681
|
-
*
|
|
11981
|
+
* 留的是这么一笔:两份文档里还写着这个函数是 `length/4`、而且指着一个它
|
|
11982
|
+
* **从来不在**的位置(`model-metadata.ts:371`)。两处都收了,
|
|
11983
|
+
* 账在 [30 §13.1](../../../../docs/verify/VERIFY_RECORD-30-web-multi-session.md):
|
|
11682
11984
|
*
|
|
11683
|
-
*
|
|
11684
|
-
*
|
|
11685
|
-
*
|
|
11686
|
-
* 拿 `threshold: 1` 能做同一件事,而且更隐蔽(开关还写着「开」)。
|
|
11687
|
-
* 反过来 `threshold: 0.01` 是让每一轮都调一次 utility 模型,**花的是用户的钱**。
|
|
11688
|
-
* ⚠️ 注意这条和 `permission` 那一条**不是**同一种坏:那一条是提权,这一条是
|
|
11689
|
-
* 「让别人的会话以一种查不出来的方式变坏」。两者都够格排除,理由要分开写 ——
|
|
11690
|
-
* 合成一句「不安全」的话,下一个人判新键时没有可比的先例
|
|
11691
|
-
* 2. **它和它的两个邻居同级。** `contextLength` 和 `maxTurns` 是设置页
|
|
11692
|
-
* 「记忆与上下文」那一节里紧挨着的两行,而 `contextLength` **也不在白名单里**。
|
|
11693
|
-
* 压缩阈值是 `contextLength` 的一个系数(乘出来才是那条线),
|
|
11694
|
-
* 让系数比被乘数更容易被仓库改,是个说不出理由的口子
|
|
11695
|
-
* 3. **写路径今天也接不住它。** `compression.enabled` 是**嵌套键**,而
|
|
11696
|
-
* [write.ts](./write.ts) 的 `writeUserYaml()` 只认不带缩进的顶层 `key:`
|
|
11697
|
-
* (`spliceTopLevel`)。放进白名单等于让设置页画出一个保存按钮,
|
|
11698
|
-
* 按下去回 `unwritable-file` —— 决定 20 ① 骂的正是这个形状。
|
|
11699
|
-
* ⚠️ 这一条**是三条里最弱的**:它描述的是今天的实现,不是设计判断。
|
|
11700
|
-
* 真要收它,前两条才是要被推翻的东西;只把 `spliceTopLevel` 改成认嵌套键
|
|
11701
|
-
* 并不足以让这个键进白名单
|
|
11985
|
+
* - `design/web-ui/README.md`「8.2 决定 8」那条 ⚠️ 划掉重写,稿子
|
|
11986
|
+
* `index.html` 那枚「估算」徽标上面那段注释同改(它是同一句话的第二处)。
|
|
11987
|
+
* - `.agents/plans/README.md`「42 划出来的底座缺口」里那一条划掉。
|
|
11702
11988
|
*
|
|
11703
|
-
*
|
|
11704
|
-
*
|
|
11705
|
-
*
|
|
11989
|
+
* ⚠️ **「要拿掉徽标得先换真 tokenizer」按交代的一个字没动** —— 它是
|
|
11990
|
+
* **必要条件**,而充分条件(同一份稿子 `thread.css` 里 `.est` 那条:
|
|
11991
|
+
* 「三个时刻里只有运行前是猜的」)今天仍然成立。两处改写都把这句话的
|
|
11992
|
+
* 必要 / 充分之分写进去了,因为它已经被读反过一次。
|
|
11706
11993
|
*/
|
|
11707
|
-
|
|
11708
11994
|
/**
|
|
11709
|
-
*
|
|
11710
|
-
* (`buildPermissionsControl` 的规则表、`explainSetting` 的标量链条)都照这个顺序
|
|
11711
|
-
* 从后往前取「最高的那一层」。
|
|
11995
|
+
* 估算一段文本的 token 数。**全仓唯一的口径**,别再各写各的。
|
|
11712
11996
|
*
|
|
11713
|
-
*
|
|
11714
|
-
*
|
|
11715
|
-
* {@link SettingsLayer} 由它派生 —— 加一层而漏了这张表,那个新值压根不在联合类型
|
|
11716
|
-
* 里,**当场编译不过**。漏一层的后果是那层的规则 / 标量被反查成 `unknown`,
|
|
11717
|
-
* 于是面板告诉用户「不知道这个值从哪来」,正好是它存在的理由本身。
|
|
11997
|
+
* @param model 留给「将来按模型选分词器」的形参,**今天被有意忽略**,
|
|
11998
|
+
* 见文件头「一套编码走天下」
|
|
11718
11999
|
*/
|
|
11719
|
-
declare
|
|
11720
|
-
|
|
11721
|
-
type SettingsLayer = (typeof SETTINGS_LAYER_ORDER)[number];
|
|
12000
|
+
declare function estimateTokens(text: string, model?: string): number;
|
|
12001
|
+
|
|
11722
12002
|
/**
|
|
11723
|
-
*
|
|
12003
|
+
* 预算闸门 —— 累加用量、判断是否该停。
|
|
11724
12004
|
*
|
|
11725
|
-
*
|
|
11726
|
-
*
|
|
11727
|
-
*
|
|
11728
|
-
*/
|
|
11729
|
-
type LayerStatus = 'loaded' | 'gated' | 'missing' | 'failed';
|
|
11730
|
-
/**
|
|
11731
|
-
* 设置文件里的**标量**键 —— `permissions` 不在这里,它走并集不走覆盖。
|
|
12005
|
+
* 判定时机由调用方(agent/loop.ts)决定,但契约是明确的:
|
|
12006
|
+
* **每轮拿到 usage 之后、发起下一轮请求之前**检查。预算的意义是阻止
|
|
12007
|
+
* 下一次花钱;请求发出去之后再检查只能事后记账,钱已经花掉了。
|
|
11732
12008
|
*
|
|
11733
|
-
*
|
|
11734
|
-
*
|
|
11735
|
-
*
|
|
12009
|
+
* 语义要点:
|
|
12010
|
+
* - token 上限与成本上限**任一**触顶即触顶
|
|
12011
|
+
* - 无定价数据时看 `onUnknownPricing`:
|
|
12012
|
+
* 'warn'(默认)token 上限照常生效、成本上限跳过,并且只告警一次
|
|
12013
|
+
* 'block' 当作触顶(前提是用户确实配了 maxCostUsd,
|
|
12014
|
+
* 否则「没配预算」不该因为查不到定价就把人拦下来)
|
|
12015
|
+
* - 一个 budget 字段都没配 → 永不触顶,此时闸门退化成纯记账器
|
|
12016
|
+
*
|
|
12017
|
+
* ## 两把尺子:跨会话累计 vs 本次调用(方案 28)
|
|
12018
|
+
*
|
|
12019
|
+
* `budget.maxCostUsd` 是「这个月别超」—— 它的计数器 `micros` 会从 `BudgetStore`
|
|
12020
|
+
* 恢复,跨进程、跨会话累加。`maxCallCostUsd`(`--max-budget-usd`)是「这一次别超」,
|
|
12021
|
+
* 计的是 `callMicros`:**从这个闸门实例被造出来那一刻起算,永不恢复、永不持久化**。
|
|
12022
|
+
*
|
|
12023
|
+
* 两把尺子叠加,谁先触到听谁的。同一轮同时触到时报跨会话那条 —— 它是更硬的墙
|
|
12024
|
+
* (调高单次上限不解决问题),先说更硬的那个才不会让用户白试一次。
|
|
11736
12025
|
*/
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
/**
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
/**
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
12026
|
+
|
|
12027
|
+
interface BudgetGuardOptions {
|
|
12028
|
+
/** 预算配置。缺省 = 不限制 */
|
|
12029
|
+
budget?: BudgetOptions;
|
|
12030
|
+
/** 当前模型单价。查不到就别传——**不要传 0 冒充免费** */
|
|
12031
|
+
pricing?: ModelPricing;
|
|
12032
|
+
/** 跨会话累计存储。配合 sessionId 才生效 */
|
|
12033
|
+
store?: BudgetStore;
|
|
12034
|
+
/** 会话 ID。给了才做持久化,否则只在内存里算单次 run */
|
|
12035
|
+
sessionId?: string;
|
|
12036
|
+
/**
|
|
12037
|
+
* **本次调用**的金额上限(美元)。对应 `--max-budget-usd`。
|
|
12038
|
+
*
|
|
12039
|
+
* 不放进 `budget`(那是 `EpochConfig.budget` 的形状,语义是跨会话累计),
|
|
12040
|
+
* 也不做成配置项:它是一次性的,配进 yaml 里等于变成第二条长期预算。
|
|
12041
|
+
*/
|
|
12042
|
+
maxCallCostUsd?: number;
|
|
12043
|
+
}
|
|
12044
|
+
declare class BudgetGuard {
|
|
12045
|
+
private readonly budget;
|
|
12046
|
+
private readonly defaultPricing;
|
|
12047
|
+
private readonly store;
|
|
12048
|
+
private readonly sessionId;
|
|
12049
|
+
/** 本次调用的金额上限(微美元)。没给就是 undefined = 不限 */
|
|
12050
|
+
private readonly callCapMicros;
|
|
12051
|
+
private usage;
|
|
12052
|
+
/** 已知定价部分的累计花费(微美元)。从没拿到过定价时为 undefined */
|
|
12053
|
+
private micros;
|
|
12054
|
+
/**
|
|
12055
|
+
* 造出来那一刻的累计花费(微美元),本次调用的**起算线**。
|
|
12056
|
+
*
|
|
12057
|
+
* 取差值而不是另记一个「本次加了多少」的计数器,是为了把 `delegate_task`
|
|
12058
|
+
* 派出去的那笔算进来:子 agent 是另一个闸门、只往 `BudgetStore` 里写,
|
|
12059
|
+
* 父这边靠 `syncFromStore()` 采纳。自己单独累加就只看得见父进程发的请求,
|
|
12060
|
+
* 而那笔钱**确实是这一次调用花的**。
|
|
12061
|
+
*/
|
|
12062
|
+
private readonly baselineMicros;
|
|
12063
|
+
/** 是否出现过「有用量但查不到定价」的轮次 */
|
|
12064
|
+
private sawUnknownPricing;
|
|
12065
|
+
private warned;
|
|
12066
|
+
private unknownPricingWarned;
|
|
12067
|
+
constructor(opts?: BudgetGuardOptions);
|
|
11750
12068
|
/**
|
|
11751
|
-
*
|
|
11752
|
-
* {@link applyScalars} 的返回值 —— 那是全流程唯一往 `config` 上写标量的地方,
|
|
11753
|
-
* 所以「这一层贡献了什么」和「这一层真的写进去了什么」不可能分叉。未信任的 ③④
|
|
11754
|
-
* 过完闸门已经没有标量了,插件层(①ᐟ⁵)压根不调 `applyScalars`,两者都是空对象。
|
|
12069
|
+
* 本次调用花掉的(微美元)。
|
|
11755
12070
|
*
|
|
11756
|
-
*
|
|
11757
|
-
*
|
|
11758
|
-
*
|
|
12071
|
+
* 从没拿到过定价时是 0 —— 「不知道花了多少」不构成拦人的理由,
|
|
12072
|
+
* 和 `breachReason()` 里成本上限遇到未知定价的处置完全一致
|
|
12073
|
+
* (那条路归 `onUnknownPricing` 管,不在这里另开一套语义)。
|
|
11759
12074
|
*/
|
|
11760
|
-
|
|
11761
|
-
/** 可直接写进 diagnostics 的一句话;`missing` 时是 undefined —— 没配过不用说 */
|
|
11762
|
-
detail?: string;
|
|
11763
|
-
}
|
|
11764
|
-
interface SettingsResolution {
|
|
11765
|
-
/** 合并后的配置。入参 `base` 不会被改动 */
|
|
11766
|
-
config: EpochConfig;
|
|
11767
|
-
layers: LayerOutcome[];
|
|
11768
|
-
issues: ParseIssue[];
|
|
12075
|
+
private callSpentMicros;
|
|
11769
12076
|
/**
|
|
11770
|
-
*
|
|
11771
|
-
*
|
|
12077
|
+
* 累加一次用量,返回累加后的状态。
|
|
12078
|
+
*
|
|
12079
|
+
* 第二个参数有三种形态,**三种语义各不相同**(方案 19 PR-1):
|
|
12080
|
+
*
|
|
12081
|
+
* | 调用形态 | 用哪个单价 | 为什么 |
|
|
12082
|
+
* | --------------------------------- | ----------------- | ----------------------------------------- |
|
|
12083
|
+
* | `add(usage)` | `defaultPricing` | 没说用了哪个模型 = 就是配置里那个 |
|
|
12084
|
+
* | `add(usage, { model, pricing })` | `pricing` | 明确知道用了谁、也知道多少钱 |
|
|
12085
|
+
* | `add(usage, { model })` | **不回落,记未知** | 知道用了谁,但查不到它的价 |
|
|
12086
|
+
*
|
|
12087
|
+
* 第三行是这个签名存在的全部理由。改造前的形态是
|
|
12088
|
+
* `add(usage, pricing?: ModelPricing)`,`pricing ?? this.defaultPricing`
|
|
12089
|
+
* 会把「查不到」悄悄换成「主模型的价」—— 而降级之后主模型的价是**错的**,
|
|
12090
|
+
* 且错得很有说服力(能算出一个像样的数字,闸门照常运转,只是永远拦不住)。
|
|
12091
|
+
*
|
|
12092
|
+
* 拿一个已知是错的数字继续算,比承认不知道更危险。
|
|
11772
12093
|
*/
|
|
11773
|
-
|
|
11774
|
-
|
|
11775
|
-
|
|
11776
|
-
|
|
11777
|
-
|
|
11778
|
-
|
|
11779
|
-
}
|
|
11780
|
-
interface ResolveSettingsOptions {
|
|
11781
|
-
/** 第 5 层:`--settings <file>`。用户在命令行显式给的,**不过信任闸门** */
|
|
11782
|
-
settingsPath?: string;
|
|
12094
|
+
add(usage: TokenUsage, rate?: {
|
|
12095
|
+
model: string;
|
|
12096
|
+
pricing?: ModelPricing;
|
|
12097
|
+
}): BudgetStatus;
|
|
12098
|
+
/** 是否已触顶。触顶后不该再发起下一次模型请求 */
|
|
12099
|
+
exceeded(): boolean;
|
|
11783
12100
|
/**
|
|
11784
|
-
*
|
|
12101
|
+
* 从存储重新同步累计,只在持久化的数字**更大**时采纳。
|
|
11785
12102
|
*
|
|
11786
|
-
*
|
|
12103
|
+
* 为什么需要:`delegate_task` 的子 agent 是另一个 `AgentLoop`、另一个
|
|
12104
|
+
* `BudgetGuard`,只是共用同一个 sessionId。父 guard 内存里持着自己那份累计,
|
|
12105
|
+
* 子任务跑完往文件里写了「父 + 子」,父的下一次 `persist()` 会拿旧的内存值
|
|
12106
|
+
* 盖回去——子任务的花费凭空消失,上限被稀释。
|
|
12107
|
+
*
|
|
12108
|
+
* 取「更大者」而不是无条件采纳:同一个会话里累计只增不减,所以更大的那份
|
|
12109
|
+
* 一定是更新的。这样也顺带扛住了别的进程同时在写同一个会话(虽然正常不该发生)。
|
|
12110
|
+
*
|
|
12111
|
+
* 调用点在**发起请求之前**,和触顶判定同一个位置——同步完再判,才判得准。
|
|
11787
12112
|
*/
|
|
11788
|
-
|
|
12113
|
+
syncFromStore(): void;
|
|
11789
12114
|
/**
|
|
11790
|
-
*
|
|
12115
|
+
* 触顶原因,没触顶就是 undefined。
|
|
11791
12116
|
*
|
|
11792
|
-
*
|
|
11793
|
-
*
|
|
12117
|
+
* 单独暴露而不是让调用方去读 status():status() 会消费掉「只报一次」的
|
|
12118
|
+
* 标记,只想问一句「能不能继续」的地方不该有副作用。
|
|
12119
|
+
*
|
|
12120
|
+
* **跨会话那三条判在前,`call-cost` 判在最后**:同一轮两把尺子都触到时,
|
|
12121
|
+
* 跨会话那条是更硬的墙(调高单次上限也过不去),先说更硬的那个。
|
|
11794
12122
|
*/
|
|
11795
|
-
|
|
12123
|
+
breachReason(): BudgetBreach | undefined;
|
|
12124
|
+
/** 跨会话累计那三条 */
|
|
12125
|
+
private sessionBreach;
|
|
12126
|
+
/** 本次调用那一条(`--max-budget-usd`) */
|
|
12127
|
+
private callBreach;
|
|
12128
|
+
/**
|
|
12129
|
+
* **预检**:再加 `extraTokens` 个 input token 会不会触顶。不会就是 undefined。
|
|
12130
|
+
*
|
|
12131
|
+
* `breachReason()` 回答的是「已经花超了吗」,只看得见 provider 事后报回来的
|
|
12132
|
+
* 数字。而图片是唯一一种**发出去之前就能估出量级**的开销(见
|
|
12133
|
+
* `context/image-tokens.ts`),所以它值得单独有个预检点:一张 4K 截图动辄
|
|
12134
|
+
* 上千 token,等 usage 回来再说,钱已经花了。
|
|
12135
|
+
*
|
|
12136
|
+
* 三条刻意的取舍:
|
|
12137
|
+
* - `extraTokens <= 0` 直接放行 —— 估不出来的输入在上游已经折成 0,
|
|
12138
|
+
* 「不知道」不构成拦人的理由
|
|
12139
|
+
* - 定价查不到时**不拦**成本上限:那条路归 `onUnknownPricing` 管
|
|
12140
|
+
* (见 `breachReason()`),预检不该另开一套「查不到就拦」的语义
|
|
12141
|
+
* - 只按 input 单价折算:图片进的是 input 侧,输出多少这里无从知道
|
|
12142
|
+
*/
|
|
12143
|
+
projectedBreach(extraTokens: number): BudgetBreach | undefined;
|
|
12144
|
+
/**
|
|
12145
|
+
* 预检不通过时给用户的一句人话。
|
|
12146
|
+
*
|
|
12147
|
+
* 和 `describeBreach()` 分开写,因为要说的不是同一件事:那边是「已经花超了,
|
|
12148
|
+
* 停」,这边是「**还没发**,发了才会超,所以没发」。把「未发送、未计费」写进
|
|
12149
|
+
* 文案是必须的 —— 否则用户看到预算提示的第一反应是「我这次的钱白花了」。
|
|
12150
|
+
*/
|
|
12151
|
+
describeProjectedBreach(breach: BudgetBreach, extraTokens: number): string;
|
|
12152
|
+
/**
|
|
12153
|
+
* 剩余额度。对应上限没配就是 undefined。
|
|
12154
|
+
*
|
|
12155
|
+
* 两把尺子都配了时 `costUsd` 报**更紧的那一份** —— 宿主拿它画进度条,
|
|
12156
|
+
* 显示一个先到不了的数字等于没显示。
|
|
12157
|
+
*/
|
|
12158
|
+
remaining(): {
|
|
12159
|
+
costUsd?: number;
|
|
12160
|
+
tokens?: number;
|
|
12161
|
+
};
|
|
12162
|
+
/**
|
|
12163
|
+
* 累计用量快照。有定价数据时带上 costUsd。
|
|
12164
|
+
*
|
|
12165
|
+
* ⚠️ 算术**借 {@link persistedUsage}**,不在这儿再写一遍:没有 guard 的时候
|
|
12166
|
+
* (上一个进程留下的会话)同一个数要从落盘那条记录里重建,两处必须逐字相同。
|
|
12167
|
+
* 判据全文在那个函数上。
|
|
12168
|
+
*/
|
|
12169
|
+
snapshot(): TokenUsage;
|
|
12170
|
+
/**
|
|
12171
|
+
* 当前状态。
|
|
12172
|
+
*
|
|
12173
|
+
* 注意 `justWarned` / `justWarnedUnknownPricing` 是**一次性**的:读到 true
|
|
12174
|
+
* 的同时标记就被消费掉了,再调用一次就是 false。告警是给人看的,
|
|
12175
|
+
* 每轮刷一遍屏就没人看了。
|
|
12176
|
+
*/
|
|
12177
|
+
status(): BudgetStatus;
|
|
12178
|
+
/** 触顶时给用户的一句人话 */
|
|
12179
|
+
describeBreach(breach: BudgetBreach): string;
|
|
12180
|
+
/**
|
|
12181
|
+
* 把一次 add 的结果翻译成给宿主的事件。
|
|
12182
|
+
*
|
|
12183
|
+
* 放在闸门里而不是循环里:`justWarned` 是一次性标记,翻译逻辑跟着标记走
|
|
12184
|
+
* 才不会出现「某条路径忘了发告警」的漂移。
|
|
12185
|
+
*
|
|
12186
|
+
* `run` 是**这一次 run 到此刻为止**的用量(方案 42 PR-2)。闸门自己算不出它 ——
|
|
12187
|
+
* 闸门的账本是累计的,而「这一次 run」的边界只有调用方知道(同一个闸门实例
|
|
12188
|
+
* 可以被 `BudgetStore` 恢复成带着上次会话的余额)。所以由 `loop.ts` 传进来,
|
|
12189
|
+
* 没有就不带这个键。
|
|
12190
|
+
*/
|
|
12191
|
+
toEvents(status: BudgetStatus, run?: TokenUsage): AgentEvent[];
|
|
12192
|
+
/** 告警文案。status().justWarned* 为 true 时给宿主拿去展示 */
|
|
12193
|
+
describeWarning(status: BudgetStatus): string;
|
|
12194
|
+
/** 已用比例,取金额(两把尺子)和 token 三个维度里更紧张的那个 */
|
|
12195
|
+
private usedRatio;
|
|
12196
|
+
private warnThreshold;
|
|
12197
|
+
private restore;
|
|
12198
|
+
private persist;
|
|
11796
12199
|
}
|
|
11797
|
-
/** 三张列表的总条数 */
|
|
11798
|
-
declare function countRules(lists: PermissionRuleLists | undefined): number;
|
|
11799
|
-
/**
|
|
11800
|
-
* 合并 ③④⑤∞ 四层文件设置到已加载的配置上。
|
|
11801
|
-
*
|
|
11802
|
-
* **会抛**:`opts.settingsPath` 指的文件不是合法 JSON 时抛 `SettingsFileError`
|
|
11803
|
-
* (方案 29 验收 #16,理由见 `finishLayer`)。其余任何一层坏了都不抛。
|
|
11804
|
-
*
|
|
11805
|
-
* @param base `loadConfig()` 的产物(第 ①② 层)。**不会被修改**
|
|
11806
|
-
* @param projectRoot 项目根,取自 `resolveProjectRoot()` —— 必须和信任判定用的是
|
|
11807
|
-
* 同一个答案,否则会出现「诊断说未信任、实际却加载了」
|
|
11808
|
-
* @param execTrusted 工作区授过**执行类**吗(`TrustDecision.exec`),取自
|
|
11809
|
-
* `decideTrust()`。本文件不自己判
|
|
11810
|
-
*
|
|
11811
|
-
* ## ⚠️ ③④ 两层**整份**归执行类,不按字段拆(2026-09-11)
|
|
11812
|
-
*
|
|
11813
|
-
* 拆档之后有一个看着更细的选法:`permissions.allow` 归执行类,`model` /
|
|
11814
|
-
* `maxTurns` 那两个标量跟着读类回来。**不这么做**,判据是这一层的 `gated`
|
|
11815
|
-
* 语义已经是「读到了、也解析了,但只有 deny 被采纳」——再按字段切一刀,
|
|
11816
|
-
* 这一层就有了三种半生不熟的状态,而设置页那条六层来源链要为每一格分别解释
|
|
11817
|
-
* 「这一格为什么在、隔壁那格为什么不在」。
|
|
11818
|
-
*
|
|
11819
|
-
* 代价如实写着:只授了读类的工作区,它 `.epoch/settings.json` 里的 `model`
|
|
11820
|
-
* 不生效 —— 和拆档之前「未信任」那一档的行为**逐字节相同**,不是新增的限制。
|
|
11821
|
-
*/
|
|
11822
|
-
declare function resolveSettings(base: EpochConfig, projectRoot: string, execTrusted: boolean, opts?: ResolveSettingsOptions): SettingsResolution;
|
|
11823
12200
|
|
|
11824
12201
|
/**
|
|
11825
|
-
*
|
|
11826
|
-
*
|
|
11827
|
-
* ```
|
|
11828
|
-
* ① 内置默认 schema 的 `.default()` ┐
|
|
11829
|
-
* ② 用户级 ~/.epoch/config.yaml ├ loader 那一相,合成 `base`
|
|
11830
|
-
* 环境变量 EPOCH_* / provider 的 API key 变量 ┘
|
|
11831
|
-
* ③ 项目级 <项目根>/.epoch/settings.json ┐
|
|
11832
|
-
* ④ 项目本地 <项目根>/.epoch/settings.local.json ├ resolveSettings 那一相
|
|
11833
|
-
* ⑤ 命令行 --settings <file> │
|
|
11834
|
-
* ∞ 企业托管 机器级 managed-settings.json ┘
|
|
11835
|
-
* ```
|
|
11836
|
-
*
|
|
11837
|
-
* ## 为什么要单独一相,而不是让 `resolveSettings` 顺手交出来
|
|
12202
|
+
* 计价 —— 把一次用量折算成钱。
|
|
11838
12203
|
*
|
|
11839
|
-
*
|
|
11840
|
-
* `config.yaml`、环境变量全拍在了一起。它分不出 ① 和 ②,而**分不出的代价是
|
|
11841
|
-
* 撒谎**:一个用户从没配过的键会被标成「② 用户级」,于是他去翻一个根本没写
|
|
11842
|
-
* 这一行的文件。所以 ①/② 那一刀由 loader 的 `getConfigOrigins()` 来切,
|
|
11843
|
-
* 本文件只负责把两相拼成一条链。
|
|
12204
|
+
* 两个刻意的设计:
|
|
11844
12205
|
*
|
|
11845
|
-
*
|
|
12206
|
+
* 1. **无定价数据返回 undefined,不返回 0**。0 的意思是「免费」,undefined 的
|
|
12207
|
+
* 意思是「不知道」。闸门对这两者的处理完全不同:免费可以随便跑,不知道
|
|
12208
|
+
* 要么告警放行、要么直接拦(见 budget-guard 的 onUnknownPricing)。
|
|
11846
12209
|
*
|
|
11847
|
-
*
|
|
11848
|
-
*
|
|
11849
|
-
*
|
|
11850
|
-
*
|
|
11851
|
-
* 只放行 `permissions` / `model` / `maxTurns`,别的键 ③④⑤∞ 碰不到 ——
|
|
11852
|
-
* 给它们画一条六层链条,就是在暗示「这一行也可能被项目级压回去」,
|
|
11853
|
-
* 而用户会照着这个暗示去 `.epoch/settings.json` 里写一个永远不生效的键。
|
|
11854
|
-
* 3. **`unknown` 是留给「分不出」的,不是给「没配过」的。** 没配过是
|
|
11855
|
-
* `builtin`,那是个确切答案。`unknown` 只在「base 里的值和内置默认不一样、
|
|
11856
|
-
* 却没有任何一处记账说是谁写的」时出现 —— 今天它够不到(loader 把
|
|
11857
|
-
* `config.yaml` 和每一个环境变量覆盖都记了账),留着是为了哪天多一条
|
|
11858
|
-
* 没记账的来路时,它显示成一个陌生的词,而不是被悄悄归到「② 用户级」。
|
|
11859
|
-
*/
|
|
11860
|
-
|
|
11861
|
-
/**
|
|
11862
|
-
* 一个配置键的值。
|
|
12210
|
+
* 2. **内部一律用「微美元整数」累加**。金额是浮点数最容易出事的地方:
|
|
12211
|
+
* 0.1 + 0.2 !== 0.3,几千轮累加下来会漂移到小数点后好几位都对不上。
|
|
12212
|
+
* micro-USD(1e-6 美元)对任何现实用量都是整数级精度,累加无损,
|
|
12213
|
+
* 只在对外暴露时除回美元。
|
|
11863
12214
|
*
|
|
11864
|
-
*
|
|
11865
|
-
* **不在这里 join 成字符串**:分隔符是排版,两份 catalog 里未必一样,
|
|
11866
|
-
* 而服务端拼一次就等于把一个界面决定钉死在网线上。
|
|
11867
|
-
*/
|
|
11868
|
-
type SettingValue = string | number | boolean | readonly string[];
|
|
11869
|
-
/**
|
|
11870
|
-
* 一个配置项的来源层。编号与叫法逐字照 [docs/SETTINGS.md](../../../../docs/SETTINGS.md)
|
|
11871
|
-
* 那张层表,**不另起一套** —— 用户在文档里读到的「③ 项目级」和界面上看到的
|
|
11872
|
-
* 必须是同一个词。
|
|
12215
|
+
* ## 各字段的口径(方案 42 PR-0 之后是**真的**在用)
|
|
11873
12216
|
*
|
|
11874
|
-
* `
|
|
11875
|
-
*
|
|
11876
|
-
*
|
|
11877
|
-
|
|
11878
|
-
|
|
11879
|
-
|
|
11880
|
-
|
|
11881
|
-
|
|
11882
|
-
|
|
11883
|
-
|
|
11884
|
-
interface SettingChain {
|
|
11885
|
-
/** 点分路径,与 `config.yaml` 里的写法逐字一致(`budget.maxCostUsd`) */
|
|
11886
|
-
key: string;
|
|
11887
|
-
/** 生效值。谁都没写、又没有内置默认时**整个不带这个键** */
|
|
11888
|
-
value?: SettingValue;
|
|
11889
|
-
/** 赢的那一层 */
|
|
11890
|
-
layer: SettingLayer;
|
|
11891
|
-
/** 六层链条,低 → 高。不在白名单里的键是**空数组**(判据见文件头第 2 条) */
|
|
11892
|
-
chain: readonly SettingStep[];
|
|
11893
|
-
}
|
|
11894
|
-
/** loader 那一相里,一个键的值是谁写的 */
|
|
11895
|
-
type BaseOrigin = 'builtin' | 'user' | 'env';
|
|
12217
|
+
* - `cacheHitTokens` / `cacheWriteTokens` 与 `inputTokens` **不重叠**,各自单独计价。
|
|
12218
|
+
* 这条契约以前是空的 —— ProviderRouter 只上报 in/out 两个数,缓存字段恒为 0,
|
|
12219
|
+
* 不是 provider 不给,是 `provider/types.ts` 把 usage 写死成两个字段截断了。
|
|
12220
|
+
* 现在 `toTokenUsage()` 按同一个口径逐字段接了进来(`inputTokens` 取的是
|
|
12221
|
+
* SDK 的 `noCacheTokens`),所以 `computeCostMicros` 里那两行缓存乘法
|
|
12222
|
+
* 从这版起是真在算钱,不再恒等于 0。
|
|
12223
|
+
* - `reasoningTokens` **包含在** `outputTokens` 里,**不单独计价、也不加进总数** ——
|
|
12224
|
+
* 加了就是把输出侧算两遍。
|
|
12225
|
+
*/
|
|
12226
|
+
|
|
11896
12227
|
/**
|
|
11897
|
-
*
|
|
12228
|
+
* 折算一次用量的花费,单位微美元(整数)。
|
|
11898
12229
|
*
|
|
11899
|
-
*
|
|
11900
|
-
*
|
|
12230
|
+
* 无定价数据返回 undefined。每个维度各自取整:单价可能是 0.0028 这种
|
|
12231
|
+
* 小数,逐维取整的误差上限是 0.5 微美元 / 维 / 次,且是确定性的,
|
|
12232
|
+
* 累加一万次也不会像浮点那样越滚越偏。
|
|
11901
12233
|
*/
|
|
11902
|
-
|
|
11903
|
-
/** `~/.epoch/config.yaml` 里真的出现过、而且**真的生效了**的键 */
|
|
11904
|
-
readonly file: ReadonlySet<string>;
|
|
11905
|
-
/** 被环境变量覆盖掉的键(`EPOCH_MODEL` / provider 的 API key 变量等) */
|
|
11906
|
-
readonly env: ReadonlySet<string>;
|
|
11907
|
-
}
|
|
11908
|
-
/** 空账。用户级什么都没写时的形状,也是 loader 起不来时的诚实答案 */
|
|
11909
|
-
declare function noConfigOrigins(): ConfigOrigins;
|
|
12234
|
+
declare function computeCostMicros(usage: TokenUsage, pricing?: ModelPricing): number | undefined;
|
|
11910
12235
|
/**
|
|
11911
|
-
*
|
|
11912
|
-
*
|
|
11913
|
-
* **环境变量优先于文件**,因为解析链就是这个顺序(loader 第 5 层压第 2 层):
|
|
11914
|
-
* 两处都写了时生效的是环境变量,说成「② 用户级」会让用户去改一个改了也不管用
|
|
11915
|
-
* 的地方。
|
|
12236
|
+
* 折算一次用量的花费,单位美元。无定价数据返回 undefined,**不是 0**。
|
|
11916
12237
|
*/
|
|
11917
|
-
declare function
|
|
11918
|
-
/**
|
|
11919
|
-
|
|
11920
|
-
|
|
11921
|
-
|
|
11922
|
-
/** `defaultConfig()` 里这个键的值 —— ① 那一格 */
|
|
11923
|
-
builtin?: SettingValue;
|
|
11924
|
-
/** 谁写的,来自 {@link originOf} */
|
|
11925
|
-
origin: BaseOrigin;
|
|
11926
|
-
}
|
|
11927
|
-
/** 白名单键才给得出的那一半:这个键在 `LayerScalars` 里叫什么、各层结果是什么 */
|
|
11928
|
-
interface FileSlots {
|
|
11929
|
-
key: ScalarKey;
|
|
11930
|
-
layers: readonly LayerOutcome[];
|
|
11931
|
-
}
|
|
12238
|
+
declare function computeCost(usage: TokenUsage, pricing?: ModelPricing): number | undefined;
|
|
12239
|
+
/** 微美元 → 美元 */
|
|
12240
|
+
declare function microsToUsd(micros: number): number;
|
|
12241
|
+
/** 美元 → 微美元(用于把用户配的 maxCostUsd 转进同一套整数体系) */
|
|
12242
|
+
declare function usdToMicros(usd: number): number;
|
|
11932
12243
|
/**
|
|
11933
|
-
*
|
|
12244
|
+
* 金额的人类可读形式。
|
|
11934
12245
|
*
|
|
11935
|
-
*
|
|
11936
|
-
*
|
|
11937
|
-
* @param file ③④⑤∞ 那一相。**不给 = 这个键不在白名单里**,于是 `chain` 是空数组
|
|
12246
|
+
* 不到 1 分钱的时候 `$0.00` 等于没说,所以小额多给两位有效数字 ——
|
|
12247
|
+
* 那条规则现在是 protocol 里 `formatMoney()` 的 `'adaptive'` 档,行为一字未改。
|
|
11938
12248
|
*
|
|
11939
|
-
*
|
|
11940
|
-
*
|
|
11941
|
-
*
|
|
12249
|
+
* ## ⚠️ 名字里的 `Usd` 说的是**入参**,不是输出
|
|
12250
|
+
*
|
|
12251
|
+
* 入参恒为美元(全仓的算术都是),而印出来的那一串跟着
|
|
12252
|
+
* `display.currency` 走 —— 配了人民币就是 `¥6.10`。这两件事的分界全文在
|
|
12253
|
+
* [protocol/src/money.ts](../../../protocol/src/money.ts) 文件头那张表。
|
|
12254
|
+
*
|
|
12255
|
+
* 名字**刻意没改成 `formatMoney`**:这个仓里 `Usd` 后缀的一贯含义就是
|
|
12256
|
+
* 「这一格装的是美元」(`costUsd`、`maxCostUsd`、`microUsd`),而它的入参正是。
|
|
12257
|
+
* 改名会让调用点看起来像是可以传别的币种进来 —— 那是这里最不想要的误解。
|
|
12258
|
+
*
|
|
12259
|
+
* @param digits 定死几位。缺省 `'adaptive'` = 这个函数的历史行为。
|
|
12260
|
+
* 闸门那几句文案给的是 `4`(**不是**口味:那几句印的是单轮 / 累计成本,
|
|
12261
|
+
* 常在 $0.003 这一档,两位会印成一个「免费」的假象)
|
|
11942
12262
|
*/
|
|
11943
|
-
declare function
|
|
11944
|
-
|
|
12263
|
+
declare function formatUsd(usd: number, digits?: MoneyDigits): string;
|
|
11945
12264
|
/**
|
|
11946
|
-
*
|
|
12265
|
+
* 累加两份用量(含缓存 / 思考 / 总数四个可选维度),返回新对象。
|
|
11947
12266
|
*
|
|
11948
|
-
*
|
|
11949
|
-
*
|
|
11950
|
-
*
|
|
11951
|
-
* 3.5 SecretStore(系统钥匙串,装配层预取后注入 —— 见 `readProviderKeyEnv`)
|
|
11952
|
-
* 4. .env 文件(secrets)
|
|
11953
|
-
* 5. process.env 直接覆盖(最高优先级)—— `EPOCH_PROVIDER` / `EPOCH_BASE_URL`
|
|
11954
|
-
* 这两个例外:它们**读一次、落两次**(第 3.5~4 层取 secrets 的前后各一次),
|
|
11955
|
-
* 理由见 `readProviderOverride` / `applyProviderOverride`
|
|
11956
|
-
* 6. 损坏回退(last-known-good → 默认值)
|
|
11957
|
-
* 7. 损坏备份(config.yaml.corrupt.<ts>.bak)
|
|
11958
|
-
* 8. 缓存(mtime + size + env_snapshot)
|
|
11959
|
-
* 9. 多 Profile(~/.epoch/profiles/<name>/)
|
|
11960
|
-
* 10. load() / loadReadonly() 双接口
|
|
12267
|
+
* 可选维度加完仍是 0 就不写进结果 —— 保住「0 是真的零,undefined 是不知道」:
|
|
12268
|
+
* 两轮都没缓存命中,累计里就不该冒出一个 `cacheHitTokens: 0` 让人以为
|
|
12269
|
+
* 这个 provider 支持缓存只是没命中。
|
|
11961
12270
|
*
|
|
11962
|
-
*
|
|
11963
|
-
* agent 起不来(safeInit 语义)。代价是必须把 issues 交出去:`loadConfig(diags)`
|
|
11964
|
-
* 的可选参数,或事后 `getConfigIssues()`。静默吞掉等于没校验。
|
|
12271
|
+
* ## ⚠️ `totalTokens` **不是**可加的维度,别照着上面三项写
|
|
11965
12272
|
*
|
|
11966
|
-
*
|
|
11967
|
-
*
|
|
12273
|
+
* 上面三项缺席当 0 加是对的(0 是加法的单位元);`totalTokens` 不是,它是一个
|
|
12274
|
+
* **总量**。`(a.totalTokens ?? 0) + (b.totalTokens ?? 0)` 在「一边报了、另一边
|
|
12275
|
+
* 没报」时收到的只有报了的那一份,而 `inputTokens` / `outputTokens` 两侧是加全
|
|
12276
|
+
* 了的 —— 于是这份累计**自相矛盾**:`totalTokens < promptTokens + outputTokens`。
|
|
12277
|
+
*
|
|
12278
|
+
* 这一档不是假想:`toTokenUsage` 明确放行了「两个总量都在、`totalTokens` 缺席」
|
|
12279
|
+
* 的 usage(OpenAI 兼容端点不带 `stream_options` 的那一家就这样),而同一段会话
|
|
12280
|
+
* 里换 provider(降级、或压缩那一发走别的模型)就会把两种 usage 加到一起。
|
|
12281
|
+
*
|
|
12282
|
+
* 症状有两层,**第二层不是显示问题**:
|
|
12283
|
+
*
|
|
12284
|
+
* 1. 屏幕上「共 X token」比旁边 `↑ + ↓` 还小(两处都读 `totalUsageTokens()`,
|
|
12285
|
+
* 而它优先信 `totalTokens`);
|
|
12286
|
+
* 2. `budget.maxTokens` 那道闸门读的是同一个数(`budget-guard.ts` 的
|
|
12287
|
+
* `sessionBreach()`),偏小 = **上限拦不住**,多跑的那几轮是真花钱的。
|
|
12288
|
+
*
|
|
12289
|
+
* 所以没报的那一侧按我们自己的口径折算一份(`totalUsageTokens()` 的兜底那一支,
|
|
12290
|
+
* 对那一侧来说是精确值),再相加。两边都没报时仍然整个不写 —— 那时
|
|
12291
|
+
* `totalUsageTokens()` 自己算出来的和这里能算出来的是同一个数,凭空多一个字段
|
|
12292
|
+
* 只会让「provider 报过」和「我们替它算的」变得分不出来。
|
|
11968
12293
|
*/
|
|
11969
|
-
|
|
11970
|
-
/** 加载配置时的覆盖项 */
|
|
11971
|
-
interface LoadConfigOptions {
|
|
11972
|
-
/**
|
|
11973
|
-
* 家目录覆盖 —— 嵌入宿主用(Electron 的 `app.getPath('userData')`)。不给时
|
|
11974
|
-
* (CLI / TUI 的常态)行为与改造前完全一致;给了就**压过**环境
|
|
11975
|
-
* (`EPOCH_HOME` / `EPOCH_PROFILE`)和 yaml 里的 `homeDir` / `dbPath`:这个选项的
|
|
11976
|
-
* 契约是「这个 runtime 实例的数据全落在这里」,让 yaml 能反过来改掉它就会出现
|
|
11977
|
-
* 「嵌入方指定了目录、引擎却写回 `~/.epoch`」。`dbPath` 必须跟着一起换 ——
|
|
11978
|
-
* SessionManager / MemoryManager / TrackerDB 都吃它,只改 homeDir 的话
|
|
11979
|
-
* 两个实例照样共用同一个 SQLite 文件。
|
|
11980
|
-
*/
|
|
11981
|
-
homeDir?: string;
|
|
11982
|
-
}
|
|
12294
|
+
declare function addUsage(a: TokenUsage, b: TokenUsage): TokenUsage;
|
|
11983
12295
|
/**
|
|
11984
|
-
*
|
|
12296
|
+
* 估算一张图片占多少 input token。
|
|
11985
12297
|
*
|
|
11986
|
-
*
|
|
11987
|
-
*
|
|
12298
|
+
* **为什么需要它**:`usage` 事件里的数字是 provider 上报的,本来就含图片 token,
|
|
12299
|
+
* 那部分不缺。缺的是**发请求之前**的估算 —— 预算闸门的检查点在请求发出前
|
|
12300
|
+
* (见 loop.ts 的注释),而 `estimateTokens()` 只按字符数算,压根看不见图片。
|
|
12301
|
+
* 于是「点一下截图,钱没了」这件事只能事后才发现。
|
|
12302
|
+
*
|
|
12303
|
+
* 用 OpenAI 的公式当统一口径:epoch-agent 的 provider 全是 OpenAI 兼容协议
|
|
12304
|
+
* (见 config/schema.ts),各家实际切法有出入,但这是个**估算**,量级对得上
|
|
12305
|
+
* 就够预算闸门用。宽高读不出来时返回 undefined —— **不是 0**。
|
|
12306
|
+
* 0 意味着「免费」,undefined 意味着「不知道」,闸门对这两者的处理必须不同
|
|
12307
|
+
* (与 `TokenUsage.costUsd` 同一条约定)。
|
|
11988
12308
|
*/
|
|
11989
|
-
declare function
|
|
11990
|
-
/** 最近一次加载的校验问题(带字段路径),空数组表示配置全合法 */
|
|
11991
|
-
declare function getConfigIssues(): ParseIssue[];
|
|
12309
|
+
declare function estimateImageTokens(width?: number, height?: number): number | undefined;
|
|
11992
12310
|
/**
|
|
11993
|
-
*
|
|
11994
|
-
*
|
|
11995
|
-
* ## 这个函数存在的理由只有一句话
|
|
12311
|
+
* 用量里计入预算的 token 总数。
|
|
11996
12312
|
*
|
|
11997
|
-
*
|
|
11998
|
-
*
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12313
|
+
* 实现住在 protocol(`totalUsageTokens`)—— TUI 的 ctx% 要用同一套口径,
|
|
12314
|
+
* 而 tui 不许 import core。这里保留这个名字只是为了不惊动 core 里既有的调用点。
|
|
12315
|
+
*/
|
|
12316
|
+
declare function totalTokens(usage: TokenUsage): number;
|
|
12317
|
+
|
|
12318
|
+
/**
|
|
12319
|
+
* 进程级的展示币种(2026-09-13)。
|
|
12002
12320
|
*
|
|
12003
|
-
* ##
|
|
12321
|
+
* ## 为什么是模块级插槽,而不是逐处穿参
|
|
12004
12322
|
*
|
|
12005
|
-
*
|
|
12006
|
-
*
|
|
12007
|
-
*
|
|
12008
|
-
*
|
|
12009
|
-
*
|
|
12010
|
-
* 那是嵌入宿主在装配期定的,既不是 ① 也不是 ②,而这两个键设置页不显示。
|
|
12011
|
-
* 哪天要显示了,它需要的是自己的一档(「宿主」),不是硬塞进现有的三档。
|
|
12323
|
+
* 判据逐条借 infra 的 `setLang()`,在这儿是原样成立的:金额的格式化点散在
|
|
12324
|
+
* core / runtime / cli / tui / web **五个包十几处**,而币种对它们中的每一个都是
|
|
12325
|
+
* **同一个进程级的一次性决定** —— 一个进程不会一半印美元一半印人民币。
|
|
12326
|
+
* 逐处穿参等于把一个显示偏好变成一次全仓签名改造,而 infra 里 `setLogLevel` /
|
|
12327
|
+
* `setShell` / `setSecretStore` 早就是这个形态。
|
|
12012
12328
|
*
|
|
12013
|
-
*
|
|
12014
|
-
* 生效的配置是它,账也该是它的。
|
|
12015
|
-
*/
|
|
12016
|
-
declare function getConfigOrigins(): ConfigOrigins;
|
|
12017
|
-
/**
|
|
12018
|
-
* provider 凭据的统一查找视图:`process.env` 优先于 `~/.epoch/.env`。
|
|
12329
|
+
* ## ⚠️ 它住在 core,不是 infra —— 因为 `CurrencyDisplay` 住在 protocol
|
|
12019
12330
|
*
|
|
12020
|
-
*
|
|
12021
|
-
*
|
|
12022
|
-
*
|
|
12023
|
-
*
|
|
12331
|
+
* infra 是**零内部依赖包**(`check-layers.mjs` 的 `ALLOWED` 里它的内部依赖是空的),
|
|
12332
|
+
* import 不了 protocol,而这个插槽装的正是一个 protocol 类型。放 infra 就得在
|
|
12333
|
+
* infra 里再声明一遍那个形状 —— 那是 `LANGS` 在两边各写一份的老问题,而那一份
|
|
12334
|
+
* 有一条「两边行为逐项相等」的门禁兜着,这一份不值得再开一条。
|
|
12024
12335
|
*
|
|
12025
|
-
*
|
|
12026
|
-
*
|
|
12336
|
+
* 代价是 **tui 够不着**(它不许 import core)。那一侧走宿主注入,形状和它那份
|
|
12337
|
+
* `t()` 逐字一样 —— 判据在 [tui/src/money.ts](../../../tui/src/money.ts)。
|
|
12027
12338
|
*
|
|
12028
|
-
* ##
|
|
12339
|
+
* ## 没设过的时候:`undefined` = 美元
|
|
12029
12340
|
*
|
|
12030
|
-
*
|
|
12341
|
+
* 这不是兜底的敷衍,它是**默认路径本身**:绝大多数用户没配 `display.currency`,
|
|
12342
|
+
* 而 `formatMoney()` 在 `currency` 缺席时的输出与 2026-09-13 之前逐字节相同
|
|
12343
|
+
* (判据在 protocol 的 `money.ts` 文件头最后一节)。
|
|
12031
12344
|
*
|
|
12032
|
-
*
|
|
12033
|
-
*
|
|
12034
|
-
*
|
|
12035
|
-
* - **`.env` 压在钥匙串之上**:它是迁移期的兼容层,也是
|
|
12036
|
-
* `epoch config secret --export` 的落点 —— 用户主动倒出来的那份如果压不过
|
|
12037
|
-
* 钥匙串里的旧值,这个逃生口就没有意义了。
|
|
12345
|
+
* ⚠️ 所以这里**不给一个 `{code:'USD', rate:1}` 的默认值**。给了的话「没配过」和
|
|
12346
|
+
* 「配了美元」在下游就分不出来了,而 `WireConfigResponse.currency` 那一格的语义
|
|
12347
|
+
* 恰恰是「没配就没有这个键」。
|
|
12038
12348
|
*/
|
|
12039
|
-
|
|
12349
|
+
|
|
12040
12350
|
/**
|
|
12041
|
-
*
|
|
12351
|
+
* 钉住这个进程的展示币种。装配层读完配置后调一次。
|
|
12042
12352
|
*
|
|
12043
|
-
*
|
|
12353
|
+
* **生产路径上只有 `cli/src/language.ts` 的 `applyConfiguredCurrency()` 调它**,
|
|
12354
|
+
* 和 `setLang()` 在同两个入口、同一个位置 —— 理由也一样:它必须跑在**任何一句
|
|
12355
|
+
* 带金额的文案之前**,而最早那几句是启动诊断里的预算行(`boot.budget_cost`)。
|
|
12044
12356
|
*
|
|
12045
|
-
*
|
|
12046
|
-
*
|
|
12047
|
-
|
|
12048
|
-
|
|
12357
|
+
* 传 `undefined` 可以把它清回美元 —— 用例需要这条(进程是复用的,
|
|
12358
|
+
* 一个用例设过币种之后不还原,下一个用例会莫名其妙拿到人民币)。
|
|
12359
|
+
*/
|
|
12360
|
+
declare function setDisplayCurrency(next: CurrencyDisplay | undefined): void;
|
|
12361
|
+
/**
|
|
12362
|
+
* 当前展示币种,没设过就是 `undefined`(= 美元)。
|
|
12049
12363
|
*
|
|
12050
|
-
*
|
|
12051
|
-
*
|
|
12364
|
+
* ⚠️ **现读,别存成模块级常量。** 常量在 `setDisplayCurrency()` 之前就求值完了,
|
|
12365
|
+
* 币种会被永久钉在「没设过」那一档 —— 同 tui 那份 `t()` 文件头「别在模块级调
|
|
12366
|
+
* `t()`」那条,失败形态一模一样:不报错,只是永远印美元。
|
|
12052
12367
|
*/
|
|
12053
|
-
declare function
|
|
12368
|
+
declare function displayCurrency(): CurrencyDisplay | undefined;
|
|
12054
12369
|
|
|
12055
12370
|
/**
|
|
12056
12371
|
* EpochConfig 的 zod schema —— **默认值的单一真源**。
|
|
@@ -12120,8 +12435,14 @@ declare function hasProviderCredentials(homeDir?: string): boolean;
|
|
|
12120
12435
|
*/
|
|
12121
12436
|
declare const DEFAULT_COMPRESSION_THRESHOLD = 0.8;
|
|
12122
12437
|
declare const EpochConfigSchema: z.ZodObject<{
|
|
12123
|
-
|
|
12124
|
-
|
|
12438
|
+
model: z.ZodOptional<z.ZodString>;
|
|
12439
|
+
models: z.ZodOptional<z.ZodObject<{
|
|
12440
|
+
utility: z.ZodOptional<z.ZodString>;
|
|
12441
|
+
}, z.core.$strip>>;
|
|
12442
|
+
modelEntries: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12443
|
+
id: z.ZodString;
|
|
12444
|
+
name: z.ZodOptional<z.ZodString>;
|
|
12445
|
+
provider: z.ZodEnum<{
|
|
12125
12446
|
openai: "openai";
|
|
12126
12447
|
anthropic: "anthropic";
|
|
12127
12448
|
google: "google";
|
|
@@ -12141,18 +12462,41 @@ declare const EpochConfigSchema: z.ZodObject<{
|
|
|
12141
12462
|
togetherai: "togetherai";
|
|
12142
12463
|
ollama: "ollama";
|
|
12143
12464
|
"openai-compatible": "openai-compatible";
|
|
12465
|
+
}>;
|
|
12466
|
+
protocol: z.ZodOptional<z.ZodEnum<{
|
|
12467
|
+
"openai-chat": "openai-chat";
|
|
12468
|
+
"anthropic-messages": "anthropic-messages";
|
|
12144
12469
|
}>>;
|
|
12145
|
-
|
|
12146
|
-
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
|
|
12151
|
-
|
|
12152
|
-
|
|
12153
|
-
|
|
12154
|
-
|
|
12155
|
-
|
|
12470
|
+
baseUrl: z.ZodOptional<z.ZodURL>;
|
|
12471
|
+
rawUrl: z.ZodOptional<z.ZodBoolean>;
|
|
12472
|
+
model: z.ZodString;
|
|
12473
|
+
capabilities: z.ZodOptional<z.ZodObject<{
|
|
12474
|
+
tools: z.ZodOptional<z.ZodBoolean>;
|
|
12475
|
+
images: z.ZodOptional<z.ZodBoolean>;
|
|
12476
|
+
reasoning: z.ZodOptional<z.ZodBoolean>;
|
|
12477
|
+
reasoningOnly: z.ZodOptional<z.ZodBoolean>;
|
|
12478
|
+
reasoningToggle: z.ZodOptional<z.ZodBoolean>;
|
|
12479
|
+
}, z.core.$strip>>;
|
|
12480
|
+
reasoning: z.ZodOptional<z.ZodObject<{
|
|
12481
|
+
levels: z.ZodArray<z.ZodEnum<{
|
|
12482
|
+
max: "max";
|
|
12483
|
+
low: "low";
|
|
12484
|
+
medium: "medium";
|
|
12485
|
+
high: "high";
|
|
12486
|
+
xhigh: "xhigh";
|
|
12487
|
+
}>>;
|
|
12488
|
+
default: z.ZodOptional<z.ZodEnum<{
|
|
12489
|
+
max: "max";
|
|
12490
|
+
low: "low";
|
|
12491
|
+
medium: "medium";
|
|
12492
|
+
high: "high";
|
|
12493
|
+
xhigh: "xhigh";
|
|
12494
|
+
}>>;
|
|
12495
|
+
}, z.core.$strip>>;
|
|
12496
|
+
contextLength: z.ZodOptional<z.ZodInt>;
|
|
12497
|
+
maxOutputTokens: z.ZodOptional<z.ZodInt>;
|
|
12498
|
+
host: z.ZodOptional<z.ZodLiteral<true>>;
|
|
12499
|
+
}, z.core.$strip>>>;
|
|
12156
12500
|
shell: z.ZodOptional<z.ZodEnum<{
|
|
12157
12501
|
cmd: "cmd";
|
|
12158
12502
|
powershell: "powershell";
|
|
@@ -12188,27 +12532,6 @@ declare const EpochConfigSchema: z.ZodObject<{
|
|
|
12188
12532
|
maxBackoffMs: z.ZodDefault<z.ZodInt>;
|
|
12189
12533
|
}, z.core.$strip>>;
|
|
12190
12534
|
fallback: z.ZodOptional<z.ZodObject<{
|
|
12191
|
-
providers: z.ZodDefault<z.ZodArray<z.ZodEnum<{
|
|
12192
|
-
openai: "openai";
|
|
12193
|
-
anthropic: "anthropic";
|
|
12194
|
-
google: "google";
|
|
12195
|
-
deepseek: "deepseek";
|
|
12196
|
-
groq: "groq";
|
|
12197
|
-
mistral: "mistral";
|
|
12198
|
-
moonshotai: "moonshotai";
|
|
12199
|
-
zhipu: "zhipu";
|
|
12200
|
-
dashscope: "dashscope";
|
|
12201
|
-
bytedance: "bytedance";
|
|
12202
|
-
qianfan: "qianfan";
|
|
12203
|
-
cohere: "cohere";
|
|
12204
|
-
xai: "xai";
|
|
12205
|
-
perplexity: "perplexity";
|
|
12206
|
-
"amazon-bedrock": "amazon-bedrock";
|
|
12207
|
-
azure: "azure";
|
|
12208
|
-
togetherai: "togetherai";
|
|
12209
|
-
ollama: "ollama";
|
|
12210
|
-
"openai-compatible": "openai-compatible";
|
|
12211
|
-
}>>>;
|
|
12212
12535
|
model: z.ZodOptional<z.ZodString>;
|
|
12213
12536
|
}, z.core.$strip>>;
|
|
12214
12537
|
hooks: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
@@ -12340,13 +12663,15 @@ declare const EpochConfigSchema: z.ZodObject<{
|
|
|
12340
12663
|
hide: "hide";
|
|
12341
12664
|
show: "show";
|
|
12342
12665
|
}>>;
|
|
12666
|
+
brandMenuId: z.ZodOptional<z.ZodString>;
|
|
12343
12667
|
sidebarMenu: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12344
12668
|
id: z.ZodString;
|
|
12345
12669
|
label: z.ZodString;
|
|
12346
12670
|
icon: z.ZodOptional<z.ZodString>;
|
|
12347
12671
|
primary: z.ZodOptional<z.ZodBoolean>;
|
|
12348
12672
|
}, z.core.$strip>>>;
|
|
12349
|
-
|
|
12673
|
+
userMenuHost: z.ZodOptional<z.ZodBoolean>;
|
|
12674
|
+
sidebarHeadMenu: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
12350
12675
|
id: z.ZodString;
|
|
12351
12676
|
label: z.ZodString;
|
|
12352
12677
|
icon: z.ZodOptional<z.ZodString>;
|
|
@@ -12504,7 +12829,8 @@ interface ConfigMigration {
|
|
|
12504
12829
|
* `permissions` 在第一张名单里、**两张都不该给它写控件**:三张列表走并集不走
|
|
12505
12830
|
* 覆盖,「加一条规则」和「把一个值改成另一个值」是两种动作、两种界面、两种确认,
|
|
12506
12831
|
* 而它的来源标注早就在安全中心那一屏上了(`PermissionsControl.rules()`)。
|
|
12507
|
-
*
|
|
12832
|
+
* 2.0.0 之前这里还有第二个例子(`fallback.providers` 那条有序的降级链),随老写法
|
|
12833
|
+
* 删了 —— 判据本身照旧:凡是「加一项 / 删一项」形状的值都不给铅笔。
|
|
12508
12834
|
*
|
|
12509
12835
|
* 那个「只写 ② 用户级就要能改任意键、得先回答 provider 段里的 key 怎么办」的
|
|
12510
12836
|
* 顾虑,答案就是**这张名单本身**:可写的键是列举出来的,`provider` 那一段
|
|
@@ -12590,17 +12916,16 @@ declare function isSettingWriteLayer(value: string): value is SettingWriteLayer;
|
|
|
12590
12916
|
* ## 收进来的判据:**它是不是一个标量旋钮**
|
|
12591
12917
|
*
|
|
12592
12918
|
* 「改一个值」这条路上能画出来的只有一个输入框加一次保存。于是收的是标量、
|
|
12593
|
-
* 拒的是别的形状 ——
|
|
12919
|
+
* 拒的是别的形状 —— 被拒的不是遗漏:
|
|
12594
12920
|
*
|
|
12595
12921
|
* - **`permissions`** 是三张列表、走并集不走覆盖,入口在安全中心(文件头第二节)
|
|
12596
|
-
* - **`
|
|
12597
|
-
*
|
|
12598
|
-
* 整条清空了,而这个键存在的理由正是「主 provider 挂了还有下家」。
|
|
12599
|
-
* 它照旧在设置页上**读**得到(`fallback.providers` 那一行印着整条链),
|
|
12600
|
-
* 只是没有铅笔
|
|
12922
|
+
* - **`provider` 那一段**(含 `apiKey`)**一个字段都不在这儿**,这就是文件头第二节
|
|
12923
|
+
* 那个「provider 段里的 key 怎么办」的答案:可写的键是列举出来的
|
|
12601
12924
|
*
|
|
12602
|
-
*
|
|
12603
|
-
*
|
|
12925
|
+
* 2.0.0 之前这一串里还有一条 `fallback.providers`(一条**有序的链** ——「往链尾
|
|
12926
|
+
* 加一家」和「把整条链换成另一条」是两种动作,一个输入框只画得出后者),随老写法
|
|
12927
|
+
* 删了。**「加一项 / 删一项」形状的值一律不收**这条判据没变,只是今天名单里
|
|
12928
|
+
* 没有这样的键可举 —— 真加进来那天,照上面那句判它。
|
|
12604
12929
|
*
|
|
12605
12930
|
* ⚠️ 加键要**同时**回答两件事,缺一件别加:这个键写进用户级 yaml 之后,
|
|
12606
12931
|
* 用户在界面上看到的会不会是真的;以及它的值域从哪儿摘({@link VALUE_SCHEMA},
|
|
@@ -12877,6 +13202,25 @@ declare function settingValueChoices(key: string): readonly string[] | null;
|
|
|
12877
13202
|
* 警告按下去的决定 —— 把用户级的默认值钉住、由项目本地临时压着,是一件正常事。
|
|
12878
13203
|
*/
|
|
12879
13204
|
declare function writeSettingValue(req: SettingWriteRequest): SettingWriteOutcome;
|
|
13205
|
+
/**
|
|
13206
|
+
* 能从 ② 用户级里**删掉**的键(2026-10-15)。只有这两个,而且只在用户级。
|
|
13207
|
+
*
|
|
13208
|
+
* 文件头第三节说「这一轮没有删键」,理由是删键「在界面上要另一个控件和另一句确认」。
|
|
13209
|
+
* 「我的模型」那一屏上副模型 / 降级模型的下拉里那一项「跟随主模型」**就是那个控件**:
|
|
13210
|
+
* 它的语义正是「这一格不配,回到下一层是什么就是什么」,而把它做成「设成主模型那一条」
|
|
13211
|
+
* 是另一件事 —— 用户之后换了主模型,副模型不会跟着换。
|
|
13212
|
+
*
|
|
13213
|
+
* 只开这两个是因为别的键没有这个控件;开成通用的就又回到了文件头那个问题。
|
|
13214
|
+
*/
|
|
13215
|
+
declare const UNSETTABLE_USER_KEYS: readonly ["models.utility", "fallback.model"];
|
|
13216
|
+
type UnsettableUserKey = (typeof UNSETTABLE_USER_KEYS)[number];
|
|
13217
|
+
/**
|
|
13218
|
+
* 从 ② 用户级里删掉一个键。手术 + 回读同 {@link writeUserYaml}:删完要能解析、
|
|
13219
|
+
* 目标字段确实不在了、**别的东西一个都没动**,有一条对不上就整个不写。
|
|
13220
|
+
*
|
|
13221
|
+
* 那个键本来就不在时是一次空操作(成功)—— 「跟随主模型」点两下不该报错。
|
|
13222
|
+
*/
|
|
13223
|
+
declare function unsetUserSetting(path: string, key: UnsettableUserKey): SettingWriteOutcome;
|
|
12880
13224
|
|
|
12881
13225
|
/**
|
|
12882
13226
|
* 设置文件的 JSON Schema 导出(方案 22 §2.7.5)。
|
|
@@ -21276,4 +21620,4 @@ declare function resolveCodeModeWorker(baseDir: string, exists?: typeof existsSy
|
|
|
21276
21620
|
*/
|
|
21277
21621
|
declare function codeModeWorkerPath(): string | undefined;
|
|
21278
21622
|
|
|
21279
|
-
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, type AcquireResult, type AgentConfig, AgentLoop, type AgentProvider, type AgentSite, type AgentSiteRef, AppearanceStore, type AppendMessageInput, ApprovalCache, type ApprovalDecision, type ArtifactOpenRefusal, type ArtifactRetention, type AskQuestionDeps, type AttachmentRetention, BLOCKED_TOOLS, type BackendOptions, type BackendOutcome, type BackgroundTaskSource, type BaseOrigin, type BaseSlot, type BeginTurnInput, type BreakdownInput, BudgetGuard, type BudgetGuardOptions, type BudgetInfo, type BudgetLevel, type BudgetOptions, type BudgetStatus, BudgetStore, type BudgetStoreOptions, type BuildFireArgvInput, type BuildIndexOptions, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, type CachedApproval, type CachedDecision, type CheckpointCapture, type CheckpointFile, CheckpointManager, type CheckpointManagerOptions, type CheckpointManifest, CheckpointStore, type CheckpointStoreOptions, type CheckpointSummary, type ClaudeHooksParseOptions, type CodeModeLimits, type CodeRequest, type CodeResult, CodeSandbox, type CommandHookConfig, type CommandHookResult, type CommandLoadOutcome, type CommandLoadSummary, type CommandResult, type CommandRunner, type CompactionResult, type CompiledRules, type ComplianceDirSpec, type ComplianceDirsSummary, ComplianceEngine, type ComplianceEngineInput, type ComplianceGate, type ComplianceHit, type ComplianceRule, type ComplianceRuleSource, type ComplianceSettings, type ComplianceVerdict, type CompressConfig, type CompressResult, type ConfigMigration, type ConfigOrigins, ContextCompressor, type ContextEngine, type CreateScheduleInput, type CreateSessionInput, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, type DelegateArgs, type DelegateConfig, DelegateManager, type DelegateTaskResult, type DiagnoseOptions, type DiscoverOutcome, type DiscoveredFile, type DoctorReport, type DraftParse, EMPTY_RULES, EPOCH_DISK_LEDGER, EpochConfigSchema, type ExpandOptions, type ExtractOutcome, FTS_TABLES, FTS_TRIGGERS, type FallbackEvent, type FileSlots, type FileSourceKind, type ForkInput, type FrontmatterParse, type FrontmatterSplit, type FtsHealth, type FtsReclaim, type FtsSpace, type FtsTableHealth, GOAL_BLOCK_CODES, type GateAxis, type GateVerdict, type GenerateInput, type GenerateOutput, type Goal, type GoalBlockReason, type GoalClear, type GoalPhase, type GoalPromptView, type GoalRefusal, type GoalRoundResult, GoalService, GoalStore, type GoalToolDeps, type GoalWrite, HEADLESS_AUDIT_CODES, type HeadlessPolicy, type HookConfigEntry, HookManager, type HookManagerOptions, type HookMatcher, type HookSourceKind, type HookSourceStatus, type HookSourcesOptions, type HookSourcesSummary, type HooksParseOutcome, type MemoryManager$1 as IMemoryManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, type SessionManager$1 as ISessionManager, type SkillSystem$1 as ISkillSystem, ImageLoadError, type ImageSize, type ImportDecision, type ImportGate, type ImportGoal, type ImportMessage, type ImportNode, type ImportPartsResult, type ImportSessionInput, type ImportSessionMeta, type ImportSessionResult, type ImportSkipReason, type ImportTodo, ImportTrustStore, type InstallDependency, type InstallOptions, type InstallOutcome, type InstallPreview, type InstallStep, type InstructionScan, type InstructionSource, type IsolationReason, type IsolationReport, type JsonSchemaDocument, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, type LastOpenSession, LastOpenSessions, type LayerOutcome, type LayerScalars, type LayerStatus, type LearningContext, type LearningResult, type LearningSkipReason, type LexiconLoadOutcome, type LifecycleOutcome, type ListCandidatesInput, type ListSessionsInput, type ListSessionsOutput, type LoadCommandsOptions, type LoadConfigOptions, type LoadKeybindingsOptions, type LoadKeybindingsResult, type LoadRolesOptions, type LoadedPlugin, type LockHolder, 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, type ManagedPolicy, ManagedPolicyError, type ManagedSettings, ManagedSettingsSchema, type MarketplaceCatalog, type MarketplaceCategory, type MarketplaceDisplay, type MarketplaceDisplayField, type MarketplaceEntry, type MarketplaceOutcome, type MarketplaceRecord, type MarketplaceStatus, type MemoryConfig, type MemoryEntry, type MemoryImportRecord, type MemoryImportResult, MemoryManager, type MemoryManagerOptions, type MemoryNotice, type MemoryRemoval, type MemorySearchResult, type MemoryTarget, type MemoryWriteResult, type MentionPart, type MentionResolution, type Message, type MessageHit, type MessageRecord, type MessageSurface, type ModelCapability, ModelFailureTracker, type ModelMetaEntry, type ModelPricing, type MultimodalPolicy, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, type NonInteractiveProbe, type OsTaskSnapshot, type OutputGuard, 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, type PackFile, type PackLimits, type PackOutcome, type PackPlan, type PackPlanOutcome, type PermissionAuditCode, type PermissionAuditEntry, type PermissionAuditOutcome, type PermissionAuditScope, type PermissionAuditSnapshot, PermissionManager, type PersistFailureKind, type PersistedBudget, type PersistedPart, type PlanApprovalFn, type PlanEnterResult, type PlanModeDeps, PlanModeState, type PlanSettlement, type PluginAbout, type PluginArtifacts, type PluginAuthor, PluginAuthorSchema, type PluginCommandDir, type PluginCounts, type PluginDoc, type PluginHookFile, type PluginInventory, type PluginLoadOptions, type PluginManifest, type PluginPathRef, type PluginRecord, type PluginSettingsFile, type PluginSourceType, type PluginStateReadOutcome, PluginStore, type PluginVisibility, type PolicyDirSpec, type PolicyDirsSummary, type PolicyLoadOutcome, type PolicyRule, PolicyRuleSchema, type PricingAt, type PricingOverride, type ProjectContext, type ProjectExtensionInventory, ProviderError, ProviderRouter, type ProviderStreamChunk, type ProviderToolCall$1 as ProviderToolCall, type ProviderWarning, type PruneResult, type QuestionAskFn, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, type ReadMessagesInput, type ReadMessagesOutput, type RecentWorkspace, RecentWorkspaces, type RecordRunInput, type RecorderOptions, type RecoveryAction, type RegisterInput, type RegisterOutcome, type RegistrarOptions, type RegistrationPatch, type RepairOutcome, type ResolveMentionsOptions, type ResolveReferenceInput, type ResolveSettingsOptions, type ResolvedCompression, type ResolvedReference, type ResolvedSource, type ResponseFormat, type RestorePartsOptions, type RewindAction, type RewindDrift, type RewindFilePlan, type RewindOptions, type RewindOutcome, type RewindPreview, type RoleCreateFailure, type RoleCreateInput, type RoleCreateOptions, type RoleCreateOutcome, type RoleLoadOutcome, type RoleLoadSummary, type RoleMergeNotice, type RoleMergeResult, type RoleRemoveFailure, type RoleRemoveOptions, type RoleRemoveOutcome, type RoleScope, type RuleMatch, type RuleSuggestion, type RuleVerdict, type RunCodeFailure, type RunCodeInput, type RunCodeOutcome, type RunCodeToolDeps, type RunEstimateInput, type RunInput, type RunResult, type RunningSchedule, 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, type ScalarKey, type ScanInstructionsOptions, type ScheduleArgvRefusal, type ScheduleArgvResult, type ScheduleArgvWarning, type ScheduleBackend, type ScheduleCliEntry, type ScheduleDraftDeps, type ScheduleDrift, type ScheduleDriftKind, type ScheduleImportInput, type ScheduleInstallForm, type ScheduleIssue, type ScheduleIssueCode, type ScheduleLock, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, type ScheduleValidation, type SchemaExport, type SearchHit, type SearchInput, type SearchMessagesInput, type SearchMessagesOutput, type SearchOutput, type SearchSessionsInput, type SelectionDeps, type SelfConnector, type SelfIndexInput, type SelfSettings, type Session, type SessionCandidate, type SessionHit, SessionManager, type SessionMentionDeps, type SessionMeta, SessionQueries, SessionReferences, type SessionSearchDeps, type SessionStats, type SessionSurface, type SessionTrace, type SessionVisibility, type SessionVisibilityInput, type SettingApply, type SettingApplyFacts, type SettingChain, type SettingFutile, type SettingLayer, type SettingStep, type SettingValue, type SettingValueKind, type SettingWriteFailure, type SettingWriteKey, type SettingWriteLayer, type SettingWriteOutcome, type SettingWriteRequest, type SettingWriteTarget, type SettingWriteTargetsOptions, type SettingsFile, SettingsFileError, SettingsFileSchema, type SettingsLayer, type SettingsResolution, type ShadowFinding, type ShadowKind, type Skill, type SkillDirSource, type SkillDirSpec, type SkillDirsSummary, type SkillImportConflict, type SkillImportDone, type SkillImportEntry, type SkillImportFailure, type SkillImportForm, type SkillImportOptions, type SkillImportPreview, type SkillImportResult, type SkillIndexEntry, type SkillIndexOptions, type SkillIndexResidency, type SkillIndexStats, SkillLearner, type SkillLearningProvider, type SkillMeta, type SkillStats, SkillSystem, type SkillSystemOptions, type SkillViewDeps, type SkillWriteDeps, type SteerSource, type StoredAppearance, type SubAgentRunner, type SubTask, type SubToolCaller, type SummarizeOutput, type SurfaceMessage, type SystemPromptParams, type SystemPromptSegments, TOOL_SEARCH_TOOL, TaskNoticeTracker, type TodoItem, type ToolCall, type ToolConflict, type ToolGate, type ToolNameFidelity, type ToolNameLookup, ToolRegistry, type ToolRevealState, type ToolScope, type ToolSearchDeps, type ToolTableCost, type TraceInput, type TraceNode, TrackerDB, type TrustDecision, type TrustGate, type TrustGateOptions, TrustManager, type TurnRecord, type TurnTiming, type UpdateScheduleInput, type UtilityModelInfo, type ValidateScheduleInput, type VersionCheck, Workspace, type WorkspaceFiles, type WorkspaceIssue, type WorkspaceIssueKind, 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, 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 };
|
|
21623
|
+
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, type AcquireResult, type AgentConfig, AgentLoop, type AgentProvider, type AgentSite, type AgentSiteRef, AppearanceStore, type AppendMessageInput, ApprovalCache, type ApprovalDecision, type ArtifactOpenRefusal, type ArtifactRetention, type AskQuestionDeps, type AttachmentRetention, BLOCKED_TOOLS, type BackendOptions, type BackendOutcome, type BackgroundTaskSource, type BaseOrigin, type BaseSlot, type BeginTurnInput, type BreakdownInput, BudgetGuard, type BudgetGuardOptions, type BudgetInfo, type BudgetLevel, type BudgetOptions, type BudgetStatus, BudgetStore, type BudgetStoreOptions, type BuildFireArgvInput, type BuildIndexOptions, CHECKPOINT_MAX_TOTAL_BYTES, CLAUDE_EVENT_NAMES, CLAUDE_TOOL_NAMES, CODE_MODE_LIMITS, CODE_MODE_SDK_TAG, CONFIG_KEYS, CURRENT_CONFIG_VERSION, type CachedApproval, type CachedDecision, type CheckpointCapture, type CheckpointFile, CheckpointManager, type CheckpointManagerOptions, type CheckpointManifest, CheckpointStore, type CheckpointStoreOptions, type CheckpointSummary, type ClaudeHooksParseOptions, type CodeModeLimits, type CodeRequest, type CodeResult, CodeSandbox, type CommandHookConfig, type CommandHookResult, type CommandLoadOutcome, type CommandLoadSummary, type CommandResult, type CommandRunner, type CompactionResult, type CompiledRules, type ComplianceDirSpec, type ComplianceDirsSummary, ComplianceEngine, type ComplianceEngineInput, type ComplianceGate, type ComplianceHit, type ComplianceRule, type ComplianceRuleSource, type ComplianceSettings, type ComplianceVerdict, type CompressConfig, type CompressResult, type ConfigMigration, type ConfigOrigins, ContextCompressor, type ContextEngine, type CreateScheduleInput, type CreateSessionInput, DEFAULT_COMPLIANCE_ACTIONS, DEFAULT_COMPLIANCE_SETTINGS, DEFAULT_COMPRESSION_THRESHOLD, DEFAULT_GOAL_MAX_ROUNDS, DEFAULT_MAX_HOLD_CHARS, type DelegateArgs, type DelegateConfig, DelegateManager, type DelegateTaskResult, type DiagnoseOptions, type DiscoverOutcome, type DiscoveredFile, type DoctorReport, type DraftParse, EMPTY_RULES, EPOCH_DISK_LEDGER, type EntryProbeCode, type EntryProbeResult, EpochConfigSchema, type ExpandOptions, type ExtractOutcome, FTS_TABLES, FTS_TRIGGERS, type FallbackEvent, type FileSlots, type FileSourceKind, type ForkInput, type FrontmatterParse, type FrontmatterSplit, type FtsHealth, type FtsReclaim, type FtsSpace, type FtsTableHealth, GOAL_BLOCK_CODES, type GateAxis, type GateVerdict, type GenerateInput, type GenerateOutput, type Goal, type GoalBlockReason, type GoalClear, type GoalPhase, type GoalPromptView, type GoalRefusal, type GoalRoundResult, GoalService, GoalStore, type GoalToolDeps, type GoalWrite, HEADLESS_AUDIT_CODES, type HeadlessPolicy, type HookConfigEntry, HookManager, type HookManagerOptions, type HookMatcher, type HookSourceKind, type HookSourceStatus, type HookSourcesOptions, type HookSourcesSummary, type HooksParseOutcome, type MemoryManager$1 as IMemoryManager, INSTRUCTION_BUDGET_CHARS, INSTRUCTION_FILES, type SessionManager$1 as ISessionManager, type SkillSystem$1 as ISkillSystem, ImageLoadError, type ImageSize, type ImportDecision, type ImportGate, type ImportGoal, type ImportMessage, type ImportNode, type ImportPartsResult, type ImportSessionInput, type ImportSessionMeta, type ImportSessionResult, type ImportSkipReason, type ImportTodo, ImportTrustStore, type InstallDependency, type InstallOptions, type InstallOutcome, type InstallPreview, type InstallStep, type InstructionScan, type InstructionSource, type IsolationReason, type IsolationReport, type JsonSchemaDocument, LAUNCHD_LABEL_PREFIX, LOCK_HEARTBEAT_MS, LOCK_STALE_MS, type LastOpenSession, LastOpenSessions, type LayerOutcome, type LayerScalars, type LayerStatus, type LearningContext, type LearningResult, type LearningSkipReason, type LexiconLoadOutcome, type LifecycleOutcome, type ListCandidatesInput, type ListSessionsInput, type ListSessionsOutput, type LoadCommandsOptions, type LoadConfigOptions, type LoadKeybindingsOptions, type LoadKeybindingsResult, type LoadRolesOptions, type LoadedPlugin, type LockHolder, 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, type ManagedPolicy, ManagedPolicyError, type ManagedSettings, ManagedSettingsSchema, type MarketplaceCatalog, type MarketplaceCategory, type MarketplaceDisplay, type MarketplaceDisplayField, type MarketplaceEntry, type MarketplaceOutcome, type MarketplaceRecord, type MarketplaceStatus, type MemoryConfig, type MemoryEntry, type MemoryImportRecord, type MemoryImportResult, MemoryManager, type MemoryManagerOptions, type MemoryNotice, type MemoryRemoval, type MemorySearchResult, type MemoryTarget, type MemoryWriteResult, type MentionPart, type MentionResolution, type Message, type MessageHit, type MessageRecord, type MessageSurface, type ModelCapability, ModelFailureTracker, type ModelMetaEntry, type ModelPricing, type MultimodalPolicy, NOMINAL_TOTAL_BYTES, NO_REVEALED_TOOLS, type NonInteractiveProbe, type OsTaskSnapshot, type OutputGuard, 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, type PackFile, type PackLimits, type PackOutcome, type PackPlan, type PackPlanOutcome, type PermissionAuditCode, type PermissionAuditEntry, type PermissionAuditOutcome, type PermissionAuditScope, type PermissionAuditSnapshot, PermissionManager, type PersistFailureKind, type PersistedBudget, type PersistedPart, type PlanApprovalFn, type PlanEnterResult, type PlanModeDeps, PlanModeState, type PlanSettlement, type PluginAbout, type PluginArtifacts, type PluginAuthor, PluginAuthorSchema, type PluginCommandDir, type PluginCounts, type PluginDoc, type PluginHookFile, type PluginInventory, type PluginLoadOptions, type PluginManifest, type PluginPathRef, type PluginRecord, type PluginSettingsFile, type PluginSourceType, type PluginStateReadOutcome, PluginStore, type PluginVisibility, type PolicyDirSpec, type PolicyDirsSummary, type PolicyLoadOutcome, type PolicyRule, PolicyRuleSchema, type PricingAt, type PricingOverride, type ProjectContext, type ProjectExtensionInventory, ProviderError, ProviderRouter, type ProviderStreamChunk, type ProviderToolCall$1 as ProviderToolCall, type ProviderWarning, type PruneResult, type QuestionAskFn, RECORDING_KEEP_RUNS, RECORDING_MAX_BYTES, ROLE_FRONTMATTER_SCHEMA, RUN_CODE_TOOL, type ReadMessagesInput, type ReadMessagesOutput, type RecentWorkspace, RecentWorkspaces, type RecordRunInput, type RecorderOptions, type RecoveryAction, type RegisterInput, type RegisterOutcome, type RegistrarOptions, type RegistrationPatch, type RepairOutcome, type ResolveMentionsOptions, type ResolveReferenceInput, type ResolveSettingsOptions, type ResolvedCompression, type ResolvedReference, type ResolvedSource, type ResponseFormat, type RestorePartsOptions, type RewindAction, type RewindDrift, type RewindFilePlan, type RewindOptions, type RewindOutcome, type RewindPreview, type RoleCreateFailure, type RoleCreateInput, type RoleCreateOptions, type RoleCreateOutcome, type RoleLoadOutcome, type RoleLoadSummary, type RoleMergeNotice, type RoleMergeResult, type RoleRemoveFailure, type RoleRemoveOptions, type RoleRemoveOutcome, type RoleScope, type RuleMatch, type RuleSuggestion, type RuleVerdict, type RunCodeFailure, type RunCodeInput, type RunCodeOutcome, type RunCodeToolDeps, type RunEstimateInput, type RunInput, type RunResult, type RunningSchedule, 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, type ScalarKey, type ScanInstructionsOptions, type ScheduleArgvRefusal, type ScheduleArgvResult, type ScheduleArgvWarning, type ScheduleBackend, type ScheduleCliEntry, type ScheduleDraftDeps, type ScheduleDrift, type ScheduleDriftKind, type ScheduleImportInput, type ScheduleInstallForm, type ScheduleIssue, type ScheduleIssueCode, type ScheduleLock, ScheduleRecorder, ScheduleRegistrar, ScheduleStore, type ScheduleValidation, type SchemaExport, type SearchHit, type SearchInput, type SearchMessagesInput, type SearchMessagesOutput, type SearchOutput, type SearchSessionsInput, type SelectionDeps, type SelfConnector, type SelfIndexInput, type SelfSettings, type Session, type SessionCandidate, type SessionHit, SessionManager, type SessionMentionDeps, type SessionMeta, SessionQueries, SessionReferences, type SessionSearchDeps, type SessionStats, type SessionSurface, type SessionTrace, type SessionVisibility, type SessionVisibilityInput, type SettingApply, type SettingApplyFacts, type SettingChain, type SettingFutile, type SettingLayer, type SettingStep, type SettingValue, type SettingValueKind, type SettingWriteFailure, type SettingWriteKey, type SettingWriteLayer, type SettingWriteOutcome, type SettingWriteRequest, type SettingWriteTarget, type SettingWriteTargetsOptions, type SettingsFile, SettingsFileError, SettingsFileSchema, type SettingsLayer, type SettingsResolution, type ShadowFinding, type ShadowKind, type Skill, type SkillDirSource, type SkillDirSpec, type SkillDirsSummary, type SkillImportConflict, type SkillImportDone, type SkillImportEntry, type SkillImportFailure, type SkillImportForm, type SkillImportOptions, type SkillImportPreview, type SkillImportResult, type SkillIndexEntry, type SkillIndexOptions, type SkillIndexResidency, type SkillIndexStats, SkillLearner, type SkillLearningProvider, type SkillMeta, type SkillStats, SkillSystem, type SkillSystemOptions, type SkillViewDeps, type SkillWriteDeps, type SteerSource, type StoredAppearance, type SubAgentRunner, type SubTask, type SubToolCaller, type SummarizeOutput, type SurfaceMessage, type SystemPromptParams, type SystemPromptSegments, TOOL_SEARCH_TOOL, TaskNoticeTracker, type TodoItem, type ToolCall, type ToolConflict, type ToolGate, type ToolNameFidelity, type ToolNameLookup, ToolRegistry, type ToolRevealState, type ToolScope, type ToolSearchDeps, type ToolTableCost, type TraceInput, type TraceNode, TrackerDB, type TrustDecision, type TrustGate, type TrustGateOptions, TrustManager, type TurnRecord, type TurnTiming, UNSETTABLE_USER_KEYS, type UpdateScheduleInput, type UtilityModelInfo, type ValidateScheduleInput, type VersionCheck, Workspace, type WorkspaceFiles, type WorkspaceIssue, type WorkspaceIssueKind, 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, 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 };
|