@sema-agent/client-core 0.7.0 → 0.8.1
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 +31 -11
- package/dist/adapt.js +36 -3
- package/dist/compensations.d.ts +63 -0
- package/dist/compensations.js +138 -0
- package/dist/detachWire.d.ts +141 -0
- package/dist/detachWire.js +182 -0
- package/dist/engineSessionParam.d.ts +6 -2
- package/dist/engineSessionParam.js +24 -18
- package/dist/engineWireTarget.d.ts +14 -0
- package/dist/engineWireTarget.js +53 -0
- package/dist/fleet/fleetLedger.d.ts +132 -0
- package/dist/fleet/fleetLedger.js +393 -0
- package/dist/fleet/fleetProjection.d.ts +181 -0
- package/dist/fleet/fleetProjection.js +258 -0
- package/dist/hooksWireCaps.d.ts +91 -0
- package/dist/hooksWireCaps.js +359 -0
- package/dist/host.d.ts +34 -2
- package/dist/host.js +6 -0
- package/dist/index.d.ts +56 -1
- package/dist/index.js +82 -1
- package/dist/liveInitToolFace.d.ts +53 -0
- package/dist/liveInitToolFace.js +251 -0
- package/dist/model/modelFamilies.json +63 -0
- package/dist/model/providerPresets.d.ts +46 -0
- package/dist/model/providerPresets.js +160 -0
- package/dist/model/providerPresets.json +1179 -0
- package/dist/seam.d.ts +19 -3
- package/dist/seam.js +14 -2
- package/dist/subagent/engineCompactWire.d.ts +12 -0
- package/dist/subagent/engineCompactWire.js +176 -0
- package/dist/subagent/engineDelegatedPrompt.d.ts +44 -0
- package/dist/subagent/engineDelegatedPrompt.js +205 -0
- package/dist/subagent/engineRowStopGate.d.ts +18 -0
- package/dist/subagent/engineRowStopGate.js +54 -0
- package/dist/subagent/engineSubagentOutput.d.ts +15 -0
- package/dist/subagent/engineSubagentOutput.js +98 -0
- package/dist/subagent/engineSubagentSteer.d.ts +10 -0
- package/dist/subagent/engineSubagentSteer.js +68 -0
- package/dist/subagent/engineSubagentTail.d.ts +40 -0
- package/dist/subagent/engineSubagentTail.js +241 -0
- package/dist/subagent/engineTaskHandleWire.d.ts +89 -0
- package/dist/subagent/engineTaskHandleWire.js +213 -0
- package/dist/workflowClient.d.ts +42 -0
- package/dist/workflowClient.js +467 -0
- package/dist/workflowMonitor.d.ts +94 -0
- package/dist/workflowMonitor.js +43 -0
- package/package.json +2 -2
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { EnvLike } from './hostEnv.js';
|
|
2
|
+
/** hands band(本地/远程执行底座在位时 prepare-task 挂载;壳自 spawn 恒 REMOTE_EXEC=host ⇒ 在位)。 */
|
|
3
|
+
export declare const ENGINE_HANDS_BAND: readonly string[];
|
|
4
|
+
/** 缺省(code/default)场景附加段 —— 与 GET /v1/capabilities/scenarios/{code,default} 详情面
|
|
5
|
+
* 实测逐字一致(场景工厂 nowTool + assembleFullBodyTools)。
|
|
6
|
+
* ⚠️ TodoWrite 故意不在(core 1.296+ 默认 taskList:true ⇒ TodoWrite 不挂,[931] 本案根因);
|
|
7
|
+
* TaskCreate 族在 —— 这两条就是本修要对齐的实况。 */
|
|
8
|
+
export declare const ENGINE_SCENARIO_EXTRAS_DEFAULT: readonly string[];
|
|
9
|
+
/** runner 恒挂段(prepare-task 按请求旋钮挂载、场景无关):壳 `-p` 恒 stamp
|
|
10
|
+
* selfOrchestration→Workflow、attachments.backgroundTasks→TaskOutput/TaskStop/Monitor/
|
|
11
|
+
* SendMessage/AgentTranscript;ReadToolResult/ReportBlocked/ReportFindings/Skill/Cron 族/
|
|
12
|
+
* ScheduleWakeup 为 runner 缺省注入。 */
|
|
13
|
+
export declare const ENGINE_RUNNER_FACE: readonly string[];
|
|
14
|
+
/** [1036]/clay 裁:runner 恒挂段按壳已知 env 门过滤——print 宿主前置 SCHEDULER_ENABLED=false
|
|
15
|
+
* (scheduler 族 headless 不挂)/SELF_ORCHESTRATION_ENABLED=false(Workflow 卸)时,自报面
|
|
16
|
+
* 必须跟实挂(此前静态词表在 env 门形态失真=自报 32 实挂 28,消融实测逮到)。 */
|
|
17
|
+
export declare function runnerFaceForEnv(env: EnvLike): string[];
|
|
18
|
+
export type LiveInitToolFace = {
|
|
19
|
+
tools: Array<{
|
|
20
|
+
name: string;
|
|
21
|
+
}>;
|
|
22
|
+
/** 'wire-vocabulary' = 缺省词表;'scenario-probe' = 非缺省场景 + 详情面探测成功。 */
|
|
23
|
+
source: 'wire-vocabulary' | 'scenario-probe';
|
|
24
|
+
};
|
|
25
|
+
/** 测试钩子:清缓存。 */
|
|
26
|
+
export declare function _resetLiveInitToolFaceForTest(): void;
|
|
27
|
+
/** 非缺省场景的详情面探测:SDK 0.0.52 scenarioCapabilities(name)(GET /v1/capabilities/scenarios/:name,
|
|
28
|
+
* 同 wire 同 encodeURIComponent)→ { tools(场景附加段), toolset }。fail-soft:任何失败(网络/404/
|
|
29
|
+
* 形状不符/超时)返回 null,调用层回退缺省词表。等价性记账:超时经 client timeoutMs(缺省 2s 同预算,
|
|
30
|
+
* maxRetries=0 单发同原语义);ScenarioDetail 类型面之外仍保留逐字段运行时校验(引擎回什么不轻信)。
|
|
31
|
+
* 差分:SDK Transport 恒 stamp x-agent-principal(缺省 'anon:shell-live')——原手抄在无 principal
|
|
32
|
+
* 时省略该头;principal-first 宪法姿势,对 requirePrincipal 部署是 fail-open 改善。 */
|
|
33
|
+
export declare function probeScenarioTools(baseUrl: string, scenario: string, opts?: {
|
|
34
|
+
fetchImpl?: typeof fetch;
|
|
35
|
+
timeoutMs?: number;
|
|
36
|
+
authToken?: string;
|
|
37
|
+
principal?: string;
|
|
38
|
+
}): Promise<{
|
|
39
|
+
tools: string[];
|
|
40
|
+
toolset?: string;
|
|
41
|
+
} | null>;
|
|
42
|
+
/**
|
|
43
|
+
* resolveLiveInitToolFace — live 车道 init 首帧 `tools` 的实挂投影。
|
|
44
|
+
* · 无 SEMA_LIVE_BASEURL ⇒ null(mock/离线车道,调用方维持静态表现状);
|
|
45
|
+
* · 缺省场景 ⇒ hands band + runner 恒挂段 + 旋钮修正(词表车道,零 IO);
|
|
46
|
+
* · 非缺省场景 ⇒ 详情面探测成功用【场景附加 + runner 恒挂】(如实标注:hands band 是否在位
|
|
47
|
+
* 由部署定,不妄报),失败回退缺省词表;
|
|
48
|
+
* · 任何内部错误 fail-soft 返回 null(调用方回退静态表,首帧永不因此缺席)。
|
|
49
|
+
*/
|
|
50
|
+
export declare function resolveLiveInitToolFace(argv: readonly string[], env: EnvLike, opts?: {
|
|
51
|
+
fetchImpl?: typeof fetch;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}): Promise<LiveInitToolFace | null>;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B6 批搬迁(2026-07-27,设计稿 §3 B6 · B2 判给后批的**宿主耦合六件**之一):cli
|
|
3
|
+
* `src/sema/liveInitToolFace.ts` 整搬。B2 当时不搬的理由 = 它 `await import('./engineTarget.js')`
|
|
4
|
+
* 取凭证,而 engineTarget 是 fs 重图;B4 的 §8-2 收编把凭证解析定到了 `resolveWireAuth`
|
|
5
|
+
* (engineWireSdk,零依赖),那条理由随之消失。
|
|
6
|
+
*
|
|
7
|
+
* 🔴 搬迁三处差分(行为一字节不变):
|
|
8
|
+
* ① 凭证:`engineTarget.resolveLiveAuthToken(baseUrl)`(其 token 缺省 = `process.env.SEMA_LIVE_TOKEN`)
|
|
9
|
+
* → `resolveWireAuth(baseUrl, env.SEMA_LIVE_TOKEN)`。两者是同一套 #118 三态,`resolveWireAuth`
|
|
10
|
+
* 的头注自述就是它的收编口。取值面完全一致:三态里只有**真 token 串**与 `'anon'` 是 string,
|
|
11
|
+
* `{mode:'loopback-unauthed'}` 是对象 ⇒ 原文那句 `typeof t === 'string' ? t : undefined` 语义照搬。
|
|
12
|
+
* ② 三个 `await import('./xxxWire.js')` fail-soft 动态 import → **静态 import**(scenarioWire /
|
|
13
|
+
* interactiveToolsWire / webSearchWireCaps 三件 B5 都已在本包内)。原文那三层 try/catch 的
|
|
14
|
+
* 目的是「模块缺席也别挡首帧」,包内它们不可能缺席;**try/catch 仍逐条保留**——它护的另一半是
|
|
15
|
+
* 「函数自己抛」,那半在包内照样成立。
|
|
16
|
+
* ③ `process.env` → 入参 `EnvLike`(签名本来就显式收 env;缺省值不存在,调用方必传)。
|
|
17
|
+
*
|
|
18
|
+
* 🔴 模块级状态:`cached`(baseUrl+scenario+旋钮 键控的一次性探测缓存)。写读口都在本文件;
|
|
19
|
+
* 两份实例只是多探一次(不是静默失效),但仍登记进单实例清单。
|
|
20
|
+
*
|
|
21
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* liveInitToolFace — 件1(core [931] 壳半场):headless `-p` live 车道 `system/init` 首帧的
|
|
24
|
+
* `tools` 自报面,从「壳静态工具表」改为「引擎实挂 wire 词表」生成。
|
|
25
|
+
*
|
|
26
|
+
* 根因(core [931]/CC209 裁决):壳自报静态表(TodoWrite 在、TaskCreate 族不在)而引擎 wire 实挂
|
|
27
|
+
* TaskCreate 族(core 1.296+ 默认)且不挂 TodoWrite ⇒ SDK/自动化消费者照自报面配
|
|
28
|
+
* `--disallowedTools TodoWrite` 之类静默无效(fail-open)。core 1.300 已在引擎侧加名单池审计
|
|
29
|
+
* advisory(config.toolpolicy.unmatched_names)兜观测面;这里是壳半场 = 自报按实挂生成。
|
|
30
|
+
*
|
|
31
|
+
* 真源层级(gap-check 2026-07-16 实测读数,server 1.214.0 / core 1.300.0):
|
|
32
|
+
* 1. server 无全量挂载探测面:GET /v1/capabilities 只有能力布尔;
|
|
33
|
+
* GET /v1/capabilities/scenarios/:name 只含【场景附加】工具(default/code →
|
|
34
|
+
* Now/WebFetch/Agent/TaskCreate 族七具),首方 band(Bash/Read/…)由 core prepare-task
|
|
35
|
+
* 另挂、不进该面 ⇒ 整表无法远端取。
|
|
36
|
+
* 2. 故缺省场景用壳已知 wire 词表(下表):观测 tap 记录壳 `-p` live 车道 LLM 请求体
|
|
37
|
+
* tools[].name(REMOTE_EXEC=host 生产姿势)实测定稿,再按壳自己 stamp 的请求旋钮修正
|
|
38
|
+
* (interactiveTools≠false → AskUserQuestion;settings.webSearch 在 → WebSearch)。
|
|
39
|
+
* 3. per-scenario 差异如实标注:非缺省 scenario(--scenario/SEMA_HEADLESS_SCENARIO)的工具面
|
|
40
|
+
* 由部署定义(scan/oa = repo-readonly 面,首方 band 是否在位壳无法确证)⇒ 尽力探场景详情面
|
|
41
|
+
* GET /v1/capabilities/scenarios/:name 报【场景附加】工具 + runner 恒挂段,并经
|
|
42
|
+
* SEMA_DEBUG 如实标注是探测面而非整表;探测失败回退缺省词表(fail-soft,绝不阻塞首帧)。
|
|
43
|
+
* 4. mock/离线车道(无 SEMA_LIVE_BASEURL)返回 null,调用方保持现状静态表 —— 宪法三问的
|
|
44
|
+
* 补偿轴:离线态回退静态表,行为字节不变。
|
|
45
|
+
*
|
|
46
|
+
* 已知局限(如实):词表按 pin 的 server/core 版本定稿,外接旧引擎或无执行底座的 worker
|
|
47
|
+
* (REMOTE_EXEC 缺省的裸 worker 不挂 hands band)会有偏差 —— 但相比旧静态表(TodoWrite 幻影 +
|
|
48
|
+
* TaskCreate 族缺席)已是严格更准的自报;整表探测面等引擎侧补(见 [931] 报告)。
|
|
49
|
+
*/
|
|
50
|
+
import { makeEngineWireClient, resolveWireAuth } from './engineWireSdk.js';
|
|
51
|
+
import { hostLog } from './host.js';
|
|
52
|
+
import { scenarioForPrint } from './scenarioWire.js';
|
|
53
|
+
import { resolveHeadlessInteractiveTools } from './interactiveToolsWire.js';
|
|
54
|
+
import { webSearchFromEnv } from './webSearchWireCaps.js';
|
|
55
|
+
// ── 壳已知 wire 词表(tap 实测 2026-07-16,server 1.214.0/core 1.300.0,壳 -p 缺省旋钮)──────────
|
|
56
|
+
/** hands band(本地/远程执行底座在位时 prepare-task 挂载;壳自 spawn 恒 REMOTE_EXEC=host ⇒ 在位)。 */
|
|
57
|
+
export const ENGINE_HANDS_BAND = [
|
|
58
|
+
'Bash',
|
|
59
|
+
'Read',
|
|
60
|
+
'Edit',
|
|
61
|
+
'Write',
|
|
62
|
+
'Glob',
|
|
63
|
+
'Grep',
|
|
64
|
+
'NotebookEdit',
|
|
65
|
+
'LSP',
|
|
66
|
+
'RepoMap',
|
|
67
|
+
'EnterWorktree',
|
|
68
|
+
'ExitWorktree',
|
|
69
|
+
'Monitor',
|
|
70
|
+
];
|
|
71
|
+
/** 缺省(code/default)场景附加段 —— 与 GET /v1/capabilities/scenarios/{code,default} 详情面
|
|
72
|
+
* 实测逐字一致(场景工厂 nowTool + assembleFullBodyTools)。
|
|
73
|
+
* ⚠️ TodoWrite 故意不在(core 1.296+ 默认 taskList:true ⇒ TodoWrite 不挂,[931] 本案根因);
|
|
74
|
+
* TaskCreate 族在 —— 这两条就是本修要对齐的实况。 */
|
|
75
|
+
export const ENGINE_SCENARIO_EXTRAS_DEFAULT = [
|
|
76
|
+
'Now',
|
|
77
|
+
'WebFetch',
|
|
78
|
+
'Agent',
|
|
79
|
+
'TaskCreate',
|
|
80
|
+
'TaskGet',
|
|
81
|
+
'TaskUpdate',
|
|
82
|
+
'TaskList',
|
|
83
|
+
];
|
|
84
|
+
/** runner 恒挂段(prepare-task 按请求旋钮挂载、场景无关):壳 `-p` 恒 stamp
|
|
85
|
+
* selfOrchestration→Workflow、attachments.backgroundTasks→TaskOutput/TaskStop/Monitor/
|
|
86
|
+
* SendMessage/AgentTranscript;ReadToolResult/ReportBlocked/ReportFindings/Skill/Cron 族/
|
|
87
|
+
* ScheduleWakeup 为 runner 缺省注入。 */
|
|
88
|
+
export const ENGINE_RUNNER_FACE = [
|
|
89
|
+
'AgentTranscript',
|
|
90
|
+
'CronCreate',
|
|
91
|
+
'CronDelete',
|
|
92
|
+
'CronList',
|
|
93
|
+
'ReadToolResult',
|
|
94
|
+
'ReportBlocked',
|
|
95
|
+
'ReportFindings',
|
|
96
|
+
'ScheduleWakeup',
|
|
97
|
+
'SendMessage',
|
|
98
|
+
'Skill',
|
|
99
|
+
'TaskOutput',
|
|
100
|
+
'TaskStop',
|
|
101
|
+
'Workflow',
|
|
102
|
+
];
|
|
103
|
+
/** [1036]/clay 裁:runner 恒挂段按壳已知 env 门过滤——print 宿主前置 SCHEDULER_ENABLED=false
|
|
104
|
+
* (scheduler 族 headless 不挂)/SELF_ORCHESTRATION_ENABLED=false(Workflow 卸)时,自报面
|
|
105
|
+
* 必须跟实挂(此前静态词表在 env 门形态失真=自报 32 实挂 28,消融实测逮到)。 */
|
|
106
|
+
export function runnerFaceForEnv(env) {
|
|
107
|
+
let face = [...ENGINE_RUNNER_FACE];
|
|
108
|
+
if (env.SCHEDULER_ENABLED === 'false') {
|
|
109
|
+
face = face.filter(n => !['CronCreate', 'CronDelete', 'CronList', 'ScheduleWakeup'].includes(n));
|
|
110
|
+
}
|
|
111
|
+
if (env.SELF_ORCHESTRATION_ENABLED === 'false') {
|
|
112
|
+
face = face.filter(n => n !== 'Workflow');
|
|
113
|
+
}
|
|
114
|
+
return face;
|
|
115
|
+
}
|
|
116
|
+
// 按 baseUrl+scenario 缓存(多 turn headless 会话只付一次探测;词表车道零 IO 本就便宜,统一缓存简单)。
|
|
117
|
+
let cached = null;
|
|
118
|
+
/** 测试钩子:清缓存。 */
|
|
119
|
+
export function _resetLiveInitToolFaceForTest() {
|
|
120
|
+
cached = null;
|
|
121
|
+
}
|
|
122
|
+
/** 非缺省场景的详情面探测:SDK 0.0.52 scenarioCapabilities(name)(GET /v1/capabilities/scenarios/:name,
|
|
123
|
+
* 同 wire 同 encodeURIComponent)→ { tools(场景附加段), toolset }。fail-soft:任何失败(网络/404/
|
|
124
|
+
* 形状不符/超时)返回 null,调用层回退缺省词表。等价性记账:超时经 client timeoutMs(缺省 2s 同预算,
|
|
125
|
+
* maxRetries=0 单发同原语义);ScenarioDetail 类型面之外仍保留逐字段运行时校验(引擎回什么不轻信)。
|
|
126
|
+
* 差分:SDK Transport 恒 stamp x-agent-principal(缺省 'anon:shell-live')——原手抄在无 principal
|
|
127
|
+
* 时省略该头;principal-first 宪法姿势,对 requirePrincipal 部署是 fail-open 改善。 */
|
|
128
|
+
export async function probeScenarioTools(baseUrl, scenario, opts) {
|
|
129
|
+
const client = makeEngineWireClient({
|
|
130
|
+
baseUrl,
|
|
131
|
+
...(opts?.authToken ? { token: opts.authToken } : {}),
|
|
132
|
+
...(opts?.principal ? { principal: opts.principal } : {}),
|
|
133
|
+
timeoutMs: opts?.timeoutMs ?? 2000,
|
|
134
|
+
...(opts?.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
|
|
135
|
+
});
|
|
136
|
+
if (!client)
|
|
137
|
+
return null;
|
|
138
|
+
try {
|
|
139
|
+
const body = await client.scenarioCapabilities(scenario);
|
|
140
|
+
if (!Array.isArray(body?.tools))
|
|
141
|
+
return null;
|
|
142
|
+
const names = body.tools.filter((t) => typeof t === 'string' && t.length > 0);
|
|
143
|
+
if (names.length === 0)
|
|
144
|
+
return null;
|
|
145
|
+
return {
|
|
146
|
+
tools: names,
|
|
147
|
+
...(typeof body.toolset === 'string' ? { toolset: body.toolset } : {}),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* resolveLiveInitToolFace — live 车道 init 首帧 `tools` 的实挂投影。
|
|
156
|
+
* · 无 SEMA_LIVE_BASEURL ⇒ null(mock/离线车道,调用方维持静态表现状);
|
|
157
|
+
* · 缺省场景 ⇒ hands band + runner 恒挂段 + 旋钮修正(词表车道,零 IO);
|
|
158
|
+
* · 非缺省场景 ⇒ 详情面探测成功用【场景附加 + runner 恒挂】(如实标注:hands band 是否在位
|
|
159
|
+
* 由部署定,不妄报),失败回退缺省词表;
|
|
160
|
+
* · 任何内部错误 fail-soft 返回 null(调用方回退静态表,首帧永不因此缺席)。
|
|
161
|
+
*/
|
|
162
|
+
export async function resolveLiveInitToolFace(argv, env, opts) {
|
|
163
|
+
const baseUrl = env.SEMA_LIVE_BASEURL;
|
|
164
|
+
if (!baseUrl)
|
|
165
|
+
return null;
|
|
166
|
+
try {
|
|
167
|
+
// 场景解析(scenarioWire 同源:flag > SEMA_HEADLESS_SCENARIO;非法 flag 在 seamQueryEngine
|
|
168
|
+
// 已 fail-loud 退出,这里只可能看到合法值或解析失败 → 按缺省处理)。
|
|
169
|
+
let scenario;
|
|
170
|
+
try {
|
|
171
|
+
const sc = scenarioForPrint([...argv], env);
|
|
172
|
+
if (sc.ok && sc.scenario)
|
|
173
|
+
scenario = sc.scenario;
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
/* fail-soft → 缺省场景 */
|
|
177
|
+
}
|
|
178
|
+
// 旋钮修正① interactiveTools:壳 `-p` 缺省 stamp false ⇒ AskUserQuestion 不挂;
|
|
179
|
+
// 显式交互意图(--permission-mode plan)/部署旋钮开 ⇒ 引擎缺省挂。
|
|
180
|
+
let askUserQuestion = false;
|
|
181
|
+
try {
|
|
182
|
+
const r = resolveHeadlessInteractiveTools([...argv], env);
|
|
183
|
+
askUserQuestion = r.fields.interactiveTools !== false;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
/* fail-soft → 按缺省 stamp false,不报 AskUserQuestion */
|
|
187
|
+
}
|
|
188
|
+
// 旋钮修正② webSearch:壳发 settings.webSearch 时 server 才组 WebSearch 后端
|
|
189
|
+
// (seamQueryEngine `-p` 车道同源 = env 面 webSearchFromEnv)。
|
|
190
|
+
let webSearch = false;
|
|
191
|
+
try {
|
|
192
|
+
webSearch = webSearchFromEnv(env) !== undefined;
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
/* fail-soft → 不报 WebSearch */
|
|
196
|
+
}
|
|
197
|
+
const knobTools = [
|
|
198
|
+
...(askUserQuestion ? ['AskUserQuestion'] : []),
|
|
199
|
+
...(webSearch ? ['WebSearch'] : []),
|
|
200
|
+
];
|
|
201
|
+
// 缓存键含旋钮(argv/env 同进程内稳定,但键面完备防同进程内旋钮变化的陈旧命中)。
|
|
202
|
+
const key = `${baseUrl}::${scenario ?? ''}::${askUserQuestion ? 'q' : ''}${webSearch ? 'w' : ''}::${env.SCHEDULER_ENABLED === 'false' ? 'ns' : ''}${env.SELF_ORCHESTRATION_ENABLED === 'false' ? 'nw' : ''}`;
|
|
203
|
+
if (cached && cached.key === key)
|
|
204
|
+
return cached.face;
|
|
205
|
+
let names;
|
|
206
|
+
let source = 'wire-vocabulary';
|
|
207
|
+
if (scenario) {
|
|
208
|
+
// 非缺省场景:详情面探测(场景附加段真源)。auth 同 live 车道纪律(#118 三态;
|
|
209
|
+
// loopback-unauthed ⇒ 不出示 bearer —— 对象形不是 string,下面那句照原文过滤掉)。
|
|
210
|
+
let authToken;
|
|
211
|
+
try {
|
|
212
|
+
const t = resolveWireAuth(baseUrl, env.SEMA_LIVE_TOKEN);
|
|
213
|
+
authToken = typeof t === 'string' ? t : undefined;
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
/* fail-soft → 无 token 探测 */
|
|
217
|
+
}
|
|
218
|
+
const probed = await probeScenarioTools(baseUrl, scenario, {
|
|
219
|
+
...(opts?.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),
|
|
220
|
+
...(opts?.timeoutMs !== undefined ? { timeoutMs: opts.timeoutMs } : {}),
|
|
221
|
+
...(authToken ? { authToken } : {}),
|
|
222
|
+
...(env.SEMA_LIVE_PRINCIPAL ? { principal: env.SEMA_LIVE_PRINCIPAL } : {}),
|
|
223
|
+
});
|
|
224
|
+
if (probed) {
|
|
225
|
+
// 如实标注:场景附加(探测面)+ runner 恒挂段;hands band 只在详情面自证 full-body 时
|
|
226
|
+
// 才报(repo-readonly 等场景不挂首方 band,不妄报)。
|
|
227
|
+
const handsBand = probed.toolset === 'full-body' ? ENGINE_HANDS_BAND : [];
|
|
228
|
+
names = [...new Set([...probed.tools, ...handsBand, ...runnerFaceForEnv(env), ...knobTools])];
|
|
229
|
+
source = 'scenario-probe';
|
|
230
|
+
if (env.SEMA_DEBUG) {
|
|
231
|
+
hostLog('debug', `[sema] init tool face: scenario "${scenario}" detail probe ok (toolset=${probed.toolset ?? 'unknown'}) — reporting scenario tools + runner face${handsBand.length > 0 ? ' + hands band' : ' (hands band not claimed for a non-full-body toolset)'}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
names = [...ENGINE_HANDS_BAND, ...ENGINE_SCENARIO_EXTRAS_DEFAULT, ...runnerFaceForEnv(env), ...knobTools];
|
|
236
|
+
if (env.SEMA_DEBUG) {
|
|
237
|
+
hostLog('debug', `[sema] init tool face: scenario "${scenario}" detail probe unavailable — falling back to the default wire vocabulary (may not match this scenario's roster)`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
names = [...ENGINE_HANDS_BAND, ...ENGINE_SCENARIO_EXTRAS_DEFAULT, ...runnerFaceForEnv(env), ...knobTools];
|
|
243
|
+
}
|
|
244
|
+
const face = { tools: names.map(name => ({ name })), source };
|
|
245
|
+
cached = { key, face };
|
|
246
|
+
return face;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return null; // 调用方回退静态表(首帧契约:永不因自报面缺席)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"families": [
|
|
3
|
+
{
|
|
4
|
+
"id": "qwen35",
|
|
5
|
+
"name": "Qwen 3.5 family",
|
|
6
|
+
"match": "^qwen3\\.5|^qwen3-",
|
|
7
|
+
"contextWindow": 1000000,
|
|
8
|
+
"maxTokens": 65536
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"id": "deepseek4",
|
|
12
|
+
"name": "DeepSeek V4 family",
|
|
13
|
+
"match": "^deepseek",
|
|
14
|
+
"contextWindow": 1000000,
|
|
15
|
+
"maxTokens": 384000,
|
|
16
|
+
"vision": false
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "claude",
|
|
20
|
+
"name": "Claude 4/5 family",
|
|
21
|
+
"match": "^claude-",
|
|
22
|
+
"contextWindow": 200000,
|
|
23
|
+
"maxTokens": 64000,
|
|
24
|
+
"vision": true
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "gpt5",
|
|
28
|
+
"name": "GPT-5 family",
|
|
29
|
+
"match": "^gpt-5",
|
|
30
|
+
"contextWindow": 400000,
|
|
31
|
+
"maxTokens": 128000,
|
|
32
|
+
"vision": true
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "kimi",
|
|
36
|
+
"name": "Kimi K3 family",
|
|
37
|
+
"match": "^kimi-",
|
|
38
|
+
"contextWindow": 262144,
|
|
39
|
+
"maxTokens": 65536
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
"id": "glm-5.2",
|
|
43
|
+
"name": "GLM 5.2 (1M ctx, Z.ai 2026-06-13)",
|
|
44
|
+
"match": "^glm-5\\.2",
|
|
45
|
+
"contextWindow": 1000000,
|
|
46
|
+
"maxTokens": 131072
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
"id": "glm",
|
|
50
|
+
"name": "GLM 4.7/5 family",
|
|
51
|
+
"match": "^glm-",
|
|
52
|
+
"contextWindow": 202800,
|
|
53
|
+
"maxTokens": 131100
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "oss",
|
|
57
|
+
"name": "Llama / other open models",
|
|
58
|
+
"match": "",
|
|
59
|
+
"contextWindow": 128000,
|
|
60
|
+
"maxTokens": 32768
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export type PresetModel = {
|
|
2
|
+
id: string;
|
|
3
|
+
contextWindow: number;
|
|
4
|
+
maxTokens: number;
|
|
5
|
+
/** Marks the provider's cheap sibling — screen-4 execution-slot recommendation. */
|
|
6
|
+
cheapHint?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export type ProviderPreset = {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
baseUrl: string;
|
|
12
|
+
api: 'anthropic-messages' | 'openai-completions';
|
|
13
|
+
authEnv: string;
|
|
14
|
+
models: PresetModel[];
|
|
15
|
+
/** Aggregator gateways (OpenRouter): model id is hand-typed, ctx/maxtok via family table. */
|
|
16
|
+
modelViaFamily?: boolean;
|
|
17
|
+
};
|
|
18
|
+
export type ModelFamily = {
|
|
19
|
+
id: string;
|
|
20
|
+
name: string;
|
|
21
|
+
/** Regex source matched against a model id to PRE-SELECT the family. '' = fallback. */
|
|
22
|
+
match: string;
|
|
23
|
+
contextWindow: number;
|
|
24
|
+
maxTokens: number;
|
|
25
|
+
/** 图像输入能力(确信才标:false=确认无 vision,true=确认有;缺省=未知不 stamp)。 */
|
|
26
|
+
vision?: boolean;
|
|
27
|
+
/**
|
|
28
|
+
* per-model 已知输出上限 —— 仅 inferFamily 第①层(providerPresets.json 精确表)命中时携带
|
|
29
|
+
* (=该行 maxTokens)。F2 裁决(clay 2026-07-15)封顶① 的证据位:家族正则/主干层的 maxTokens
|
|
30
|
+
* 是家族参考值,不是该模型的确证 cap,不带此位。
|
|
31
|
+
*/
|
|
32
|
+
perModelCap?: number;
|
|
33
|
+
};
|
|
34
|
+
export declare const PROVIDER_PRESETS: ProviderPreset[];
|
|
35
|
+
export declare const MODEL_FAMILIES: ModelFamily[];
|
|
36
|
+
/** Infer context/max from a model id — four evidence tiers, strongest first. */
|
|
37
|
+
export declare function inferFamily(modelId: string): ModelFamily | undefined;
|
|
38
|
+
/** Format token counts like "1m" / "384k". */
|
|
39
|
+
export declare function fmtTokens(n: number): string;
|
|
40
|
+
/** "1m ctx / 384k out" style short metric text. */
|
|
41
|
+
export declare function fmtCtxOut(ctx: number, out: number): string;
|
|
42
|
+
/** Screen-4 execution-slot recommendation: cheapHint first, else a cheap-sounding sibling. */
|
|
43
|
+
export declare function pickCheapCandidate<T extends {
|
|
44
|
+
id: string;
|
|
45
|
+
cheapHint?: boolean;
|
|
46
|
+
}>(models: T[], defaultId: string): T | undefined;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ⇄ B6 批搬迁(2026-07-27,设计稿 §3 B6 · B4「四残件」之 model 数据表):cli
|
|
3
|
+
* `src/sema/onboard/providerPresets.{ts,json}` + `modelFamilies.json` 整搬(零依赖、纯查表)。
|
|
4
|
+
*
|
|
5
|
+
* 🔴 **为什么数据表必须进包**:`modelBudgetRule.defaultMaxTokensFor()`(B2 已搬)的封顶① 参数
|
|
6
|
+
* `perModelCap` 只有一个来源 —— `inferFamily()` 第①层精确命中。表留在壳里 ⇒ 库里那条中央规则
|
|
7
|
+
* 在 web/桌面上**永远拿不到 cap**,`defaultMaxTokensFor(ctx)` 恒走 min(base, ctx/4),
|
|
8
|
+
* 对 deepseek-chat(cap 8192)这类模型直接超发 ⇒ 网关 400。这不是「少个装饰」。
|
|
9
|
+
*
|
|
10
|
+
* 🔴 **两份副本的漂移锁**:搬迁期壳树那两个 JSON 还在(壳侧 shim 批之前 onboard 全族在读它),
|
|
11
|
+
* 所以 pure 门加了一条 **byte-identity 断言**:本包 `src/model/*.json` 与壳树
|
|
12
|
+
* `src/sema/onboard/*.json` 必须**逐字节相等**,任一侧加一行 provider 而另一侧没加即当场红。
|
|
13
|
+
* 壳侧 shim 落地(那两个 JSON 删除、providerPresets.ts 改 re-export 本包)之后,该断言
|
|
14
|
+
* 自动降为 SKIP(壳树缺席 = 搬完了),不会变成陈旧的假红。
|
|
15
|
+
*
|
|
16
|
+
* ── 以下为原文件的领域说明(逐字保留)────────────────────────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* providerPresets.ts — typed accessors over the static preset data tables for
|
|
19
|
+
* `sema onboard` (GUIDE-ONBOARD-DESIGN §3a/§3b, clay 拍板 2026-07-03).
|
|
20
|
+
*
|
|
21
|
+
* DATA lives in the sibling JSON files (providerPresets.json /
|
|
22
|
+
* modelFamilies.json) — adding a provider or family is a table row, no code
|
|
23
|
+
* change (维护性原则: new source = one module + one table row). Shapes align
|
|
24
|
+
* with openclaw's plugin-manifest `modelCatalog.providers` / CenterModel so
|
|
25
|
+
* translators stay ~free, but centralized (sema has no plugin system).
|
|
26
|
+
*
|
|
27
|
+
* All numbers are best-effort defaults (待核 official docs); qwen/glm values
|
|
28
|
+
* were read from clay's real ~/.openclaw catalog.
|
|
29
|
+
*/
|
|
30
|
+
import presetData from './providerPresets.json' with { type: 'json' };
|
|
31
|
+
import familyData from './modelFamilies.json' with { type: 'json' };
|
|
32
|
+
export const PROVIDER_PRESETS = presetData.presets;
|
|
33
|
+
export const MODEL_FAMILIES = familyData.families;
|
|
34
|
+
// ── 模型自动匹配 v2(clay 07-06:预填要"较为准确的推荐",真实测例驱动,verify/_infer-family-test)──
|
|
35
|
+
// 四层:①preset 大表精确命中(46 provider 的真实 models,最强证据)②容量后缀/名内提示([1m]、-128k)
|
|
36
|
+
// ③家族主干匹配(部署前缀/参数量/量化后缀容忍)④family 表前缀兜底。
|
|
37
|
+
/** 尾部变体词(主干提取时剥):不承载 ctx 信息的营销/角色/量化/参数量后缀。 */
|
|
38
|
+
const VARIANT_TAIL = /(-(plus|pro|max|flash|turbo|mini|lite|chat|coder?|code|instruct|thinking|reasoner|next|preview|latest|fp8|fp16|awq|int[48]|gptq|\d+b))+$/;
|
|
39
|
+
let presetIndexCache = null;
|
|
40
|
+
function presetIndex() {
|
|
41
|
+
if (presetIndexCache)
|
|
42
|
+
return presetIndexCache;
|
|
43
|
+
const exact = new Map();
|
|
44
|
+
const stems = [];
|
|
45
|
+
for (const p of PROVIDER_PRESETS) {
|
|
46
|
+
for (const m of p.models ?? []) {
|
|
47
|
+
if (!m?.id || !m.contextWindow)
|
|
48
|
+
continue;
|
|
49
|
+
const hit = {
|
|
50
|
+
id: m.id.toLowerCase(),
|
|
51
|
+
contextWindow: m.contextWindow,
|
|
52
|
+
maxTokens: m.maxTokens ?? 32768,
|
|
53
|
+
famId: p.id,
|
|
54
|
+
};
|
|
55
|
+
if (!exact.has(hit.id))
|
|
56
|
+
exact.set(hit.id, hit);
|
|
57
|
+
stems.push(hit);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
presetIndexCache = { exact, stems };
|
|
61
|
+
return presetIndexCache;
|
|
62
|
+
}
|
|
63
|
+
function normalizeId(raw) {
|
|
64
|
+
let id = raw.trim().toLowerCase();
|
|
65
|
+
const slash = id.lastIndexOf('/');
|
|
66
|
+
if (slash >= 0)
|
|
67
|
+
id = id.slice(slash + 1); // org/model → model
|
|
68
|
+
id = id.replace(/\[[0-9]+[km]\]$/, ''); // [1m] 容量后缀在第②层单独消费
|
|
69
|
+
return id;
|
|
70
|
+
}
|
|
71
|
+
/** Infer context/max from a model id — four evidence tiers, strongest first. */
|
|
72
|
+
export function inferFamily(modelId) {
|
|
73
|
+
const raw = modelId.trim().toLowerCase();
|
|
74
|
+
if (!raw)
|
|
75
|
+
return undefined;
|
|
76
|
+
const idx = presetIndex();
|
|
77
|
+
// ② 前置:显式容量标注恒赢([1m]/[200k] CC 惯例后缀)——用户/工具已声明,别猜。
|
|
78
|
+
const capSuffix = raw.match(/\[(\d+)([km])\]$/);
|
|
79
|
+
if (capSuffix) {
|
|
80
|
+
const n = Number(capSuffix[1]);
|
|
81
|
+
const ctx = capSuffix[2] === 'm' ? n * 1000000 : n * 1024;
|
|
82
|
+
const base = inferFamily(raw.replace(/\[[0-9]+[km]\]$/, ''));
|
|
83
|
+
// perModelCap 透传:后缀只声明 ctx,不改该模型的确证输出 cap(deepseek-chat[1m] 仍 cap 8192)
|
|
84
|
+
return { id: base?.id ?? 'cap-suffix', name: base?.name ?? 'capacity suffix', match: '', contextWindow: ctx, maxTokens: base?.maxTokens ?? 64000, vision: base?.vision, perModelCap: base?.perModelCap };
|
|
85
|
+
}
|
|
86
|
+
const id = normalizeId(raw);
|
|
87
|
+
// ① preset 大表精确(含剥变体尾巴后再试)——精确命中才带 perModelCap(F2 裁决封顶① 证据位;
|
|
88
|
+
// ③ 层主干匹配拿的是家族参考行,不是该模型的确证 cap)
|
|
89
|
+
const hitOf = (m, exact = false) => ({ id: m.famId, name: m.famId, match: '', contextWindow: m.contextWindow, maxTokens: m.maxTokens, ...(exact ? { perModelCap: m.maxTokens } : {}) });
|
|
90
|
+
const exact = idx.exact.get(id);
|
|
91
|
+
if (exact)
|
|
92
|
+
return hitOf(exact, true);
|
|
93
|
+
const stripped = id.replace(VARIANT_TAIL, '');
|
|
94
|
+
if (stripped !== id) {
|
|
95
|
+
const e2 = idx.exact.get(stripped);
|
|
96
|
+
if (e2)
|
|
97
|
+
return hitOf(e2, true);
|
|
98
|
+
}
|
|
99
|
+
// ② 名内容量提示(moonshot-v1-128k / *-32k):k=×1024
|
|
100
|
+
const inline = id.match(/-(\d+)([km])\b/);
|
|
101
|
+
if (inline) {
|
|
102
|
+
const n = Number(inline[1]);
|
|
103
|
+
if ((inline[2] === 'k' && n >= 8) || inline[2] === 'm') {
|
|
104
|
+
const ctx = inline[2] === 'm' ? n * 1000000 : n * 1024;
|
|
105
|
+
return { id: 'inline-cap', name: 'capacity in id', match: '', contextWindow: ctx, maxTokens: Math.min(ctx, 65536) };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
// ③ 家族主干:候选主干=preset model id 剥变体尾;主干出现在待匹配 id 中,取最长主干
|
|
109
|
+
let best = null;
|
|
110
|
+
let bestLen = 0;
|
|
111
|
+
for (const m of idx.stems) {
|
|
112
|
+
const stem = m.id.replace(VARIANT_TAIL, '');
|
|
113
|
+
if (stem.length >= 6 && id.includes(stem)) {
|
|
114
|
+
// 同长命中取 ctx 最小=家族原生保守值(服务档扩展如 DashScope 1M 让用户在档位里升,
|
|
115
|
+
// 预填错大比错小危险——超发请求直接被网关拒)
|
|
116
|
+
if (stem.length > bestLen || (stem.length === bestLen && best && m.contextWindow < best.contextWindow)) {
|
|
117
|
+
best = m;
|
|
118
|
+
bestLen = stem.length;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (best)
|
|
123
|
+
return hitOf(best);
|
|
124
|
+
// ④ family 表前缀(anchored → unanchored)
|
|
125
|
+
for (const fam of MODEL_FAMILIES) {
|
|
126
|
+
if (fam.match && new RegExp(fam.match, 'i').test(id))
|
|
127
|
+
return fam;
|
|
128
|
+
}
|
|
129
|
+
for (const fam of MODEL_FAMILIES) {
|
|
130
|
+
if (fam.match && new RegExp(fam.match.replace(/\^/g, ''), 'i').test(id))
|
|
131
|
+
return fam;
|
|
132
|
+
}
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
/** Format token counts like "1m" / "384k". */
|
|
136
|
+
export function fmtTokens(n) {
|
|
137
|
+
// token 界惯例=二进制 K(clay 07-06:32768 显示 33K 是错的,就是 32K;262144=256K)。
|
|
138
|
+
// 1024 整除走二进制;否则十进制圆整(200000→200k、202800→203k);M 档十进制(1000000→1m)。
|
|
139
|
+
// #29⑤(prelaunch audit):十进制整千先判 —— 128000 是十进制圆数,必须渲 128k,不是 125k
|
|
140
|
+
// (128000%1024==0 曾把它劫进二进制档;其它入口全都叫 128K)。32768/262144/393216 不整除 1000,不受影响。
|
|
141
|
+
if (n >= 1000000 && n % 100000 === 0)
|
|
142
|
+
return `${n / 1000000}m`;
|
|
143
|
+
if (n >= 1000 && n % 1000 === 0)
|
|
144
|
+
return `${n / 1000}k`;
|
|
145
|
+
if (n >= 1024 && n % 1024 === 0)
|
|
146
|
+
return `${n / 1024}k`;
|
|
147
|
+
if (n >= 1000)
|
|
148
|
+
return `${Math.round(n / 1000)}k`;
|
|
149
|
+
return String(n);
|
|
150
|
+
}
|
|
151
|
+
/** "1m ctx / 384k out" style short metric text. */
|
|
152
|
+
export function fmtCtxOut(ctx, out) {
|
|
153
|
+
return `${fmtTokens(ctx)} ctx / ${fmtTokens(out)} out`;
|
|
154
|
+
}
|
|
155
|
+
/** Screen-4 execution-slot recommendation: cheapHint first, else a cheap-sounding sibling. */
|
|
156
|
+
export function pickCheapCandidate(models, defaultId) {
|
|
157
|
+
const others = models.filter(m => m.id !== defaultId);
|
|
158
|
+
return (others.find(m => m.cheapHint) ??
|
|
159
|
+
others.find(m => /flash|mini|lite|turbo|air|haiku|small/i.test(m.id)));
|
|
160
|
+
}
|