dsh-subagent-profile 0.3.2 → 0.3.4
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 +77 -40
- package/README.zh.md +111 -74
- package/docs/screenshots/dispatch-card.png +0 -0
- package/docs/screenshots/settings-page1.png +0 -0
- package/docs/screenshots/settings-page2.png +0 -0
- package/index.mjs +276 -81
- package/lib/client.js +3218 -166
- package/lib/core/adoption-reminder.mjs +48 -0
- package/lib/core/adoption-tracker.mjs +430 -0
- package/lib/core/background-ledger.mjs +71 -0
- package/lib/core/catalog-cache.mjs +45 -7
- package/lib/core/catalog.mjs +6 -6
- package/lib/core/cost-evidence.mjs +145 -0
- package/lib/core/cost-guard.mjs +71 -44
- package/lib/core/decision-trace.mjs +413 -0
- package/lib/core/delegation.mjs +111 -50
- package/lib/core/dispatch-gates.mjs +153 -0
- package/lib/core/dispatch-guard.mjs +156 -0
- package/lib/core/dispatch-schema.mjs +103 -14
- package/lib/core/dispatch-tool.mjs +220 -204
- package/lib/core/draft-gates.mjs +45 -0
- package/lib/core/drafts-store.mjs +45 -0
- package/lib/core/escape.mjs +130 -0
- package/lib/core/evolution-advice.mjs +224 -0
- package/lib/core/evolution-ledger.mjs +300 -0
- package/lib/core/evolution-summary.mjs +255 -0
- package/lib/core/http-routes.mjs +256 -72
- package/lib/core/intersection.mjs +6 -9
- package/lib/core/presets-sync.mjs +161 -43
- package/lib/core/prices.mjs +46 -0
- package/lib/core/profile-directory.mjs +139 -0
- package/lib/core/profile-provider.mjs +42 -39
- package/lib/core/profiles-store.mjs +103 -76
- package/lib/core/pure.mjs +110 -66
- package/lib/core/reminder-store.mjs +172 -0
- package/lib/core/shims.mjs +67 -76
- package/lib/core/whitelist.mjs +23 -17
- package/package.json +82 -83
- package/presets/orchestrator/agent.cordis.yml +59 -87
- package/presets/orchestrator/NOTICE +0 -3
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// lib/core/cost-evidence.mjs — 省 token 证据(反事实对照,纯函数)。
|
|
2
|
+
// 判定读 trace.requested.model(不能读 effective.model:buildMeta 会把继承的父
|
|
3
|
+
// 模型名写进 effective,无法区分「继承父」与「显式选 pro」)。未指定 model 时
|
|
4
|
+
// saving=0 并打标「未指定模型(继承父)」,让根因①可见。
|
|
5
|
+
//
|
|
6
|
+
// 成本口径:B 级五段单价可用时按 usage 逐段计价(元/千 token);否则回退 A 级
|
|
7
|
+
// 单次均价(docs/cost-closed-loop-design.md L14 实测)。无价格/无 usage/无模型
|
|
8
|
+
// 一律 fail-soft 返回 undefined,不显示不猜数。
|
|
9
|
+
|
|
10
|
+
import { MODEL_AVG_COST, MODEL_USAGE_PRICES, priceKeyFor } from './prices.mjs';
|
|
11
|
+
|
|
12
|
+
function costFromUsage(model, usage) {
|
|
13
|
+
const key = priceKeyFor(model);
|
|
14
|
+
if (key === undefined) return undefined;
|
|
15
|
+
const prices = MODEL_USAGE_PRICES[key];
|
|
16
|
+
if (prices === undefined) return undefined;
|
|
17
|
+
let total = 0;
|
|
18
|
+
let any = false;
|
|
19
|
+
for (const seg of ['inputTokens', 'outputTokens', 'cacheReadTokens', 'cacheWriteTokens', 'reasoningTokens']) {
|
|
20
|
+
const unit = prices[seg];
|
|
21
|
+
const amount = usage?.[seg];
|
|
22
|
+
if (typeof unit === 'number' && Number.isFinite(unit) && typeof amount === 'number' && Number.isFinite(amount) && amount >= 0) {
|
|
23
|
+
total += (unit * amount) / 1000;
|
|
24
|
+
any = true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return any ? Number(total.toFixed(6)) : undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 单条派发的估算成本(元)。优先五段计价,其次 A 级单次均价。
|
|
31
|
+
export function dispatchCost({ model, usage } = {}) {
|
|
32
|
+
const key = priceKeyFor(model);
|
|
33
|
+
if (key === undefined) return undefined;
|
|
34
|
+
const byUsage = costFromUsage(model, usage);
|
|
35
|
+
if (byUsage !== undefined) return byUsage;
|
|
36
|
+
const avg = MODEL_AVG_COST[key];
|
|
37
|
+
return typeof avg === 'number' ? avg : undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 反事实对照结果。record 为 dispatch.jsonl 行;显式指定 model 才有「继承父 vs
|
|
41
|
+
// 指定 model」价差,未指定(继承父)saving 恒 0 并打 inherit 标。
|
|
42
|
+
export function counterfactualFor(record) {
|
|
43
|
+
if (record === null || typeof record !== 'object') return undefined;
|
|
44
|
+
const requestedModel = record.requested?.model;
|
|
45
|
+
const usage = record.outcome?.usage;
|
|
46
|
+
const parentModel = record.parent_model;
|
|
47
|
+
if (typeof requestedModel !== 'string' || requestedModel === '') {
|
|
48
|
+
const parentCost = dispatchCost({ model: parentModel, usage });
|
|
49
|
+
return {
|
|
50
|
+
inherit: true,
|
|
51
|
+
model: parentModel ?? null,
|
|
52
|
+
actual: parentCost,
|
|
53
|
+
counterfactual: parentCost,
|
|
54
|
+
saving: 0,
|
|
55
|
+
ratio: null,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const actual = dispatchCost({ model: requestedModel, usage });
|
|
59
|
+
const counterfactual = dispatchCost({ model: parentModel, usage });
|
|
60
|
+
if (actual === undefined || counterfactual === undefined) return undefined;
|
|
61
|
+
const saving = Number((counterfactual - actual).toFixed(6));
|
|
62
|
+
return {
|
|
63
|
+
inherit: false,
|
|
64
|
+
model: requestedModel,
|
|
65
|
+
parentModel: parentModel ?? null,
|
|
66
|
+
actual,
|
|
67
|
+
counterfactual,
|
|
68
|
+
saving,
|
|
69
|
+
ratio: actual > 0 ? Number((counterfactual / actual).toFixed(2)) : null,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 聚合一组 record 的估算成本与继承占比。只统计可计价(actual 非 undefined)记录。
|
|
74
|
+
export function summarizeCosts(records) {
|
|
75
|
+
let actualSum = 0;
|
|
76
|
+
let counterfactualSum = 0;
|
|
77
|
+
let savingSum = 0;
|
|
78
|
+
let priced = 0;
|
|
79
|
+
let inheritCount = 0;
|
|
80
|
+
for (const record of records) {
|
|
81
|
+
if (record === null || typeof record !== 'object') continue;
|
|
82
|
+
const c = counterfactualFor(record);
|
|
83
|
+
if (c === undefined) continue;
|
|
84
|
+
if (c.actual === undefined) continue;
|
|
85
|
+
actualSum += c.actual;
|
|
86
|
+
if (c.counterfactual !== undefined) counterfactualSum += c.counterfactual;
|
|
87
|
+
savingSum += c.saving;
|
|
88
|
+
priced += 1;
|
|
89
|
+
if (c.inherit) inheritCount += 1;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
estimated_cost: Number(actualSum.toFixed(4)),
|
|
93
|
+
estimated_inherit_cost: Number(counterfactualSum.toFixed(4)),
|
|
94
|
+
estimated_saving: Number(savingSum.toFixed(4)),
|
|
95
|
+
priced,
|
|
96
|
+
inherit_count: inheritCount,
|
|
97
|
+
inherit_ratio: priced > 0 ? Number((inheritCount / priced).toFixed(3)) : 0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 从 summaries.json 的 l1/l2 聚合成本汇总:全局(所有 L1 组求和)与按模型轴
|
|
102
|
+
// (l2 的 :model: 键,同一模型多组归并)。同一记录只属一个 L1 组与一个 model 轴键,
|
|
103
|
+
// 故全局与模型轴各自无重复。无数据返回空汇总(调用方省略显示)。
|
|
104
|
+
export function costSummaryFromSummaries(summaries) {
|
|
105
|
+
const root = summaries !== null && typeof summaries === 'object' ? summaries : {};
|
|
106
|
+
const l1 = root.l1 !== null && typeof root.l1 === 'object' ? root.l1 : {};
|
|
107
|
+
const l2 = root.l2 !== null && typeof root.l2 === 'object' ? root.l2 : {};
|
|
108
|
+
const global = { estimated_cost: 0, estimated_inherit_cost: 0, estimated_saving: 0, priced: 0, inherit_count: 0 };
|
|
109
|
+
for (const group of Object.values(l1)) {
|
|
110
|
+
const c = group !== null && typeof group === 'object' ? group.cost : null;
|
|
111
|
+
if (c === null || typeof c !== 'object') continue;
|
|
112
|
+
if (typeof c.estimated_cost === 'number') global.estimated_cost += c.estimated_cost;
|
|
113
|
+
if (typeof c.estimated_inherit_cost === 'number') global.estimated_inherit_cost += c.estimated_inherit_cost;
|
|
114
|
+
if (typeof c.estimated_saving === 'number') global.estimated_saving += c.estimated_saving;
|
|
115
|
+
if (typeof c.priced === 'number') global.priced += c.priced;
|
|
116
|
+
if (typeof c.inherit_count === 'number') global.inherit_count += c.inherit_count;
|
|
117
|
+
}
|
|
118
|
+
global.estimated_cost = Number(global.estimated_cost.toFixed(4));
|
|
119
|
+
global.estimated_inherit_cost = Number(global.estimated_inherit_cost.toFixed(4));
|
|
120
|
+
global.estimated_saving = Number(global.estimated_saving.toFixed(4));
|
|
121
|
+
global.inherit_ratio = global.priced > 0 ? Number((global.inherit_count / global.priced).toFixed(3)) : 0;
|
|
122
|
+
const byModel = new Map();
|
|
123
|
+
for (const [key, group] of Object.entries(l2)) {
|
|
124
|
+
if (group === null || typeof group !== 'object') continue;
|
|
125
|
+
const match = /:model:([^|]+)$/.exec(key);
|
|
126
|
+
if (match === null) continue;
|
|
127
|
+
const model = match[1];
|
|
128
|
+
const cur = byModel.get(model) ?? { model, estimated_cost: 0, priced: 0, deployments: 0 };
|
|
129
|
+
const c = group.cost;
|
|
130
|
+
if (c !== null && typeof c === 'object') {
|
|
131
|
+
if (typeof c.estimated_cost === 'number') cur.estimated_cost += c.estimated_cost;
|
|
132
|
+
if (typeof c.priced === 'number') cur.priced += c.priced;
|
|
133
|
+
}
|
|
134
|
+
if (typeof group.deployments_total === 'number') cur.deployments += group.deployments_total;
|
|
135
|
+
byModel.set(model, cur);
|
|
136
|
+
}
|
|
137
|
+
const modelRows = [...byModel.values()].map((m) => ({
|
|
138
|
+
model: m.model,
|
|
139
|
+
deployments: m.deployments,
|
|
140
|
+
estimated_cost: Number(m.estimated_cost.toFixed(4)),
|
|
141
|
+
priced: m.priced,
|
|
142
|
+
avg_cost: m.priced > 0 ? Number((m.estimated_cost / m.priced).toFixed(4)) : null,
|
|
143
|
+
})).sort((a, b) => (a.model < b.model ? -1 : a.model > b.model ? 1 : 0));
|
|
144
|
+
return { global, byModel: modelRows };
|
|
145
|
+
}
|
package/lib/core/cost-guard.mjs
CHANGED
|
@@ -2,30 +2,28 @@
|
|
|
2
2
|
// 仅引用 lib/core/pure.mjs 的 assertHardLimits;无 @deepseek-ai 依赖。
|
|
3
3
|
//
|
|
4
4
|
// 运行时推导的 cost guard,两部分:
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// to verify and always passes (valid in a headless deployment).
|
|
5
|
+
// * always-on hard caps(assertHardLimits,在 lib/core/pure.mjs)——maxTokens /
|
|
6
|
+
// maxDepth 是硬性委派上限,与 `llm` 服务无关,llm 缺失时绝不能停止生效
|
|
7
|
+
// (历史教训:早期版本在 llm 缺失时直接 return,连带跳过硬上限,属缺陷——
|
|
8
|
+
// 硬上限永远前置)。
|
|
9
|
+
// * llm 能力校验——对 live provider 目录校验 provider / model /
|
|
10
|
+
// reasoningEffort。`llm` 服务缺失或其 provider 目录为空(无发现能力的
|
|
11
|
+
// adapter)时能力无法核验:按 `allowFailOpen`(迁移开关)二选一——
|
|
12
|
+
// fail-open 兼容(warn + 跳过;v1 数据迁移中)或 fail-loud 拒绝。profile
|
|
13
|
+
// 若 provider/model/reasoningEffort 都未请求,无校验项,恒通过(无头部署
|
|
14
|
+
// 下合法)。
|
|
16
15
|
//
|
|
17
16
|
// 目录读取改走共享 catalog 快照(lib/core/catalog-cache.mjs):assertCostGuard 的
|
|
18
17
|
// `catalog` 参数是缓存实例,`catalog.getSnapshot(llm)` 返回缓存过的 providers /
|
|
19
18
|
// modelsByProvider。缓存只提速目录解析、不复检安全门——provider/model/effort 的
|
|
20
19
|
// 校验逻辑与 fail-loud 文案逐字不变;reasoningEffort 走直连 resolveCallConfig
|
|
21
20
|
// (校验调用,非目录读取,不入缓存)。
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// module-scoped and cannot reach the apply closure's `allowFailOpen`/`ctx.logger`.
|
|
21
|
+
// 本函数同时供 provider 权威校验与 dispatch 工具预检使用。`allowFailOpen`/`logger`
|
|
22
|
+
// 经注入传入:函数是模块级作用域,够不到 apply 闭包里的 `allowFailOpen`/`ctx.logger`。
|
|
25
23
|
|
|
26
24
|
import { assertHardLimits } from './pure.mjs';
|
|
27
25
|
|
|
28
|
-
//
|
|
26
|
+
// 仅当 profile 请求了需核验能力面的字段时才进入 llm 校验(无头/headless 部署下
|
|
29
27
|
// persona-only / toolFilter-only 的 profile 合法,不应被 fail-loud 拒绝)。
|
|
30
28
|
function needsLlmCheck(profile) {
|
|
31
29
|
return ['provider', 'model', 'reasoningEffort'].some(
|
|
@@ -42,23 +40,22 @@ function handleUnverifiable(allowFailOpen, logger) {
|
|
|
42
40
|
throw new Error('dispatch: 模型能力不可验证:fail-loud 拒绝(可在配置中显式开启兼容模式)');
|
|
43
41
|
}
|
|
44
42
|
|
|
45
|
-
//
|
|
43
|
+
// provider 注册校验(目录非空时才能判定「不在目录」)。providers 来自 catalog 快照。
|
|
46
44
|
function assertProviderRegistered(dir, profile) {
|
|
47
45
|
if (typeof profile.provider !== 'string' || profile.provider.length === 0) return;
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
const providers = Array.isArray(dir.providers) ? dir.providers : [];
|
|
47
|
+
if (!providers.some((provider) => provider && provider.id === profile.provider)) {
|
|
48
|
+
throw new Error(`dispatch: provider "${profile.provider}" 未注册(不在当前 provider 目录中,可在设置页改用已注册的 provider)`);
|
|
50
49
|
}
|
|
51
50
|
}
|
|
52
51
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
// modelsByProvider 来自快照;快照未覆盖的 provider(undefined / 不在名册)
|
|
61
|
-
// 走直连 listModels 兜底,保持原 fail-loud 文案逐字。
|
|
52
|
+
// model 校验。resolveModelInfo 不拒绝未知模型(目录成员关系只是建议性的),
|
|
53
|
+
// 所以改为对照已发布的目录校验。空目录(无发现能力的 adapter)无法核验,
|
|
54
|
+
// 跳过——目录级空集已在 llm 门走 allowFailOpen 分支,此处仅兜底
|
|
55
|
+
// per-provider 空目录。非空目录未发布该模型则 fail-loud。不可核验的查询
|
|
56
|
+
// (provider 未知时 listModels(undefined))变成干净的 fail-loud 错误,而不是
|
|
57
|
+
// 泄漏 "undefined"。modelsByProvider 来自快照;快照未覆盖的 provider
|
|
58
|
+
// (undefined / 不在名册)走直连 listModels 兜底,保持原 fail-loud 文案逐字。
|
|
62
59
|
async function assertModelAdvertised(llm, profile, effectiveProvider, modelsByProvider) {
|
|
63
60
|
if (typeof profile.model !== 'string' || profile.model.length === 0) return;
|
|
64
61
|
const cached = modelsByProvider !== undefined ? modelsByProvider[String(effectiveProvider)] : undefined;
|
|
@@ -75,48 +72,78 @@ async function assertModelAdvertised(llm, profile, effectiveProvider, modelsByPr
|
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
74
|
if (modelsError !== undefined) {
|
|
78
|
-
throw new Error(`dispatch:
|
|
75
|
+
throw new Error(`dispatch: 无法校验模型 "${profile.model}":provider 目录查询失败(${modelsError instanceof Error ? modelsError.message : String(modelsError)}),可稍后重试或在设置页关闭模型能力校验`, { cause: modelsError });
|
|
79
76
|
}
|
|
80
77
|
const listed = models ?? [];
|
|
81
78
|
const known = listed.length > 0 && listed.some((model) => model && (model.id === profile.model || model.name === profile.model));
|
|
82
79
|
if (listed.length > 0 && !known) {
|
|
83
|
-
throw new Error(`dispatch:
|
|
80
|
+
throw new Error(`dispatch: 模型 "${profile.model}" 不在 provider "${String(effectiveProvider)}" 的目录中(可换用该 provider 已发布的模型)`);
|
|
84
81
|
}
|
|
85
82
|
}
|
|
86
83
|
|
|
87
|
-
//
|
|
84
|
+
// reasoningEffort 校验。
|
|
88
85
|
async function assertReasoningSupported(llm, profile, effectiveProvider, effectiveModel) {
|
|
89
86
|
if (typeof profile.reasoningEffort !== 'string' || profile.reasoningEffort.length === 0) return;
|
|
90
87
|
try {
|
|
91
88
|
await llm.resolveCallConfig({ provider: effectiveProvider, model: effectiveModel, reasoningEffort: profile.reasoningEffort });
|
|
92
89
|
} catch (error) {
|
|
93
|
-
throw new Error(`dispatch: reasoningEffort "${profile.reasoningEffort}"
|
|
90
|
+
throw new Error(`dispatch: reasoningEffort "${profile.reasoningEffort}" 不受 provider "${String(effectiveProvider)}" 的模型 "${String(effectiveModel)}" 支持(${error instanceof Error ? error.message : String(error)}),可改用一个该模型支持的 effort 值`, { cause: error });
|
|
94
91
|
}
|
|
95
92
|
}
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
// 能力面逐项校验的统一接线:判定通过回调 verdict:'pass';失败先回调 verdict:'fail'
|
|
95
|
+
// (含 error.message 作 detail)再 rethrow 原错误(文案不变)。
|
|
96
|
+
async function runChecked(check, field, checkedAgainst, fn) {
|
|
97
|
+
try {
|
|
98
|
+
await fn();
|
|
99
|
+
check({ field, checkedAgainst, verdict: 'pass' });
|
|
100
|
+
} catch (error) {
|
|
101
|
+
check({ field, checkedAgainst, verdict: 'fail', detail: error.message });
|
|
102
|
+
throw error;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// llm 门的逐项记录:verdict 对应实际走向,detail 注明 fail-open/fail-loud。
|
|
107
|
+
function checkLlmGate(check, allowFailOpen, detailFailLoud, detailFailOpen) {
|
|
108
|
+
check({ field: 'llm', verdict: allowFailOpen === true ? 'pass' : 'fail', detail: allowFailOpen === true ? detailFailOpen : detailFailLoud });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export async function assertCostGuard(parent, profile, allowFailOpen, logger, catalog, onCheck) {
|
|
112
|
+
// 可选逐项回调;onCheck 缺省时归一为 no-op,行为逐字不变(现有调用点不加参数,零漂移)。
|
|
113
|
+
const check = typeof onCheck === 'function' ? onCheck : () => {};
|
|
114
|
+
|
|
115
|
+
// 硬上限 always-on(不依赖 llm)。
|
|
99
116
|
assertHardLimits(profile.maxTokens, profile.maxDepth);
|
|
100
117
|
|
|
101
|
-
//
|
|
102
|
-
if (!needsLlmCheck(profile))
|
|
118
|
+
// 无 llm 能力面字段 → 无可核验,直接通过(headless 部署合法)。
|
|
119
|
+
if (!needsLlmCheck(profile)) {
|
|
120
|
+
check({ field: 'llm', verdict: 'pass', detail: '无需核验(未请求 provider/model/reasoningEffort)' });
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
103
123
|
|
|
104
124
|
const llm = parent.ctx.get('llm');
|
|
105
|
-
//
|
|
106
|
-
if (llm === undefined)
|
|
125
|
+
// llm 缺失 → 无法核验:按 allowFailOpen 决定 fail-open / fail-loud。
|
|
126
|
+
if (llm === undefined) {
|
|
127
|
+
checkLlmGate(check, allowFailOpen, 'llm 缺失:fail-loud 拒绝', 'llm 缺失:fail-open 跳过核验');
|
|
128
|
+
return handleUnverifiable(allowFailOpen, logger);
|
|
129
|
+
}
|
|
107
130
|
|
|
108
131
|
// llm 存在:目录读取走共享 catalog 快照(缓存只提速目录解析,校验逻辑不变)。
|
|
109
132
|
const snapshot = await catalog.getSnapshot(llm);
|
|
110
|
-
|
|
111
|
-
|
|
133
|
+
// 快照形状守卫——畸形快照(llm 缺失 / providers 非数组)不得被静默放行,
|
|
134
|
+
// 归一为「目录为空」走同一 fail-loud / fail-open 门。
|
|
135
|
+
const dir = (snapshot !== null && typeof snapshot === 'object' && snapshot.llm !== null && typeof snapshot.llm === 'object') ? snapshot.llm : {};
|
|
136
|
+
const providers = Array.isArray(dir.providers) ? dir.providers : [];
|
|
137
|
+
if (dir.providersError !== undefined || providers.length === 0) {
|
|
138
|
+
checkLlmGate(check, allowFailOpen, 'provider 目录为空:fail-loud 拒绝', 'provider 目录为空:fail-open 跳过核验');
|
|
112
139
|
return handleUnverifiable(allowFailOpen, logger);
|
|
113
140
|
}
|
|
114
141
|
|
|
115
|
-
//
|
|
116
|
-
assertProviderRegistered(dir, profile);
|
|
142
|
+
// provider 注册校验。
|
|
143
|
+
await runChecked(check, 'provider', 'catalogProviders', () => assertProviderRegistered(dir, profile));
|
|
117
144
|
const effectiveProvider = profile.provider !== undefined ? profile.provider : parent.options.provider;
|
|
118
145
|
const effectiveModel = profile.model !== undefined ? profile.model : parent.options.model;
|
|
119
|
-
//
|
|
120
|
-
await assertModelAdvertised(llm, profile, effectiveProvider, dir.modelsByProvider);
|
|
121
|
-
await assertReasoningSupported(llm, profile, effectiveProvider, effectiveModel);
|
|
146
|
+
// model 校验 + reasoningEffort 校验。
|
|
147
|
+
await runChecked(check, 'model', 'providerModelList', () => assertModelAdvertised(llm, profile, effectiveProvider, dir.modelsByProvider));
|
|
148
|
+
await runChecked(check, 'reasoningEffort', 'resolveCallConfig', () => assertReasoningSupported(llm, profile, effectiveProvider, effectiveModel));
|
|
122
149
|
}
|