@sema-agent/client-core 0.6.0 → 0.8.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 +31 -11
- package/dist/adapt.d.ts +4 -2
- package/dist/adapt.js +225 -25
- 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/diff/patch.d.ts +29 -0
- package/dist/diff/patch.js +45 -0
- package/dist/engineSessionParam.d.ts +11 -0
- package/dist/engineSessionParam.js +41 -0
- package/dist/engineWireTarget.d.ts +14 -0
- package/dist/engineWireTarget.js +53 -0
- package/dist/finalVerifyWire.d.ts +74 -0
- package/dist/finalVerifyWire.js +63 -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/headlessPermissionModeWire.d.ts +55 -0
- package/dist/headlessPermissionModeWire.js +111 -0
- package/dist/headlessReconnectWire.d.ts +96 -0
- package/dist/headlessReconnectWire.js +141 -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 +74 -1
- package/dist/index.js +104 -1
- package/dist/interactiveToolsWire.d.ts +48 -0
- package/dist/interactiveToolsWire.js +86 -0
- package/dist/limitsWire.d.ts +89 -0
- package/dist/limitsWire.js +225 -0
- 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/notifications.d.ts +7 -0
- package/dist/notifications.js +32 -0
- package/dist/sandboxWire.d.ts +75 -0
- package/dist/sandboxWire.js +138 -0
- package/dist/scenarioWire.d.ts +62 -0
- package/dist/scenarioWire.js +115 -0
- package/dist/seam.d.ts +52 -5
- package/dist/seam.js +15 -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 +90 -0
- package/dist/subagent/engineTaskHandleWire.js +212 -0
- package/dist/toolResult.d.ts +118 -0
- package/dist/toolResult.js +774 -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 +5 -3
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/sema/limitsWire.ts — headless `-p` run-limits wire (TB2.0 反馈批 E · P2-3-b, 2026-07-15):
|
|
3
|
+
* expose the engine's REQUEST-LEVEL pacing limits on the headless `-p` path via `TaskRequest.limits`.
|
|
4
|
+
* The server accepts `limits:{timeoutSec?,maxOutputTokens?,maxTurns?}` since 1.196.0 (positive-integer
|
|
5
|
+
* 400 gate, board [857]) and feeds the EXISTING pacing machinery (deadlineNudge / callCapByDeadline /
|
|
6
|
+
* gracefulFinalize) — the shell simply never stamped the field. This module is the CLI face.
|
|
7
|
+
*
|
|
8
|
+
* TWO FLAGS, TWO ALIGNMENT BUCKETS:
|
|
9
|
+
* · `--deadline <sec>` → limits.timeoutSec. Upstream CC 2.1.207 has NO deadline flag (nearest kin:
|
|
10
|
+
* --max-turns / --task-budget / --max-budget-usd) → SUPERSET, help copy says so.
|
|
11
|
+
* · `--max-turns <n>` → limits.maxTurns. Upstream CC 2.1.207 HAS `--max-turns` (source 199676 /
|
|
12
|
+
* 793380) → ALIGNMENT-GAP fill: the flag was already registered (main.tsx CC-inherited surface) and
|
|
13
|
+
* drove the local query loop, but the seam path executes turns ENGINE-side — this wire finally
|
|
14
|
+
* carries the cap to where the turns actually run.
|
|
15
|
+
*
|
|
16
|
+
* PRECEDENCE per knob (flag > settings > none), scenarioWire 三件套同款:
|
|
17
|
+
* a) explicit flag → strict validation, FAIL-LOUD on an invalid
|
|
18
|
+
* value (integer domain below; a mis-typed pacing bound must not quietly run unbounded).
|
|
19
|
+
* b) settings env lane (settings.json `env` block → 1a-envseed → process.env):
|
|
20
|
+
* `SEMA_HEADLESS_DEADLINE_SEC` / `SEMA_HEADLESS_MAX_TURNS` → SILENT degrade on an invalid value
|
|
21
|
+
* (spec'd 静默降级 — a settings typo must never brick every headless run; unlike scenarioWire we
|
|
22
|
+
* don't even warn, per the batch-E triage shape. SEMA_DEBUG surfaces the drop for diagnosis).
|
|
23
|
+
* c) neither → NO stamp = today's behaviour, verbatim.
|
|
24
|
+
*
|
|
25
|
+
* DOMAINS (integer, inclusive): timeoutSec 30..86400 · maxTurns 1..1000.
|
|
26
|
+
*
|
|
27
|
+
* VERSION GATE (triage risk ③ — an OLD server SILENTLY SWALLOWS unknown body fields, worse than not
|
|
28
|
+
* shipping): the self-spawned engine is release-pinned ≥1.197.0 so the dev/release lanes are safe by
|
|
29
|
+
* construction; but a user-supplied SEMA_ENGINE_URL may point at an older engine. When the user
|
|
30
|
+
* EXPLICITLY passed a flag (env-lane defaults stay quiet) and SEMA_ENGINE_URL is in play, the caller
|
|
31
|
+
* probes `${baseUrl}/health` (the engine self-describes `version` since 1.96) and prints ONE honest
|
|
32
|
+
* stderr line when version <1.196.0 — the request still goes out unchanged (warn, don't block).
|
|
33
|
+
* Unreachable /health or an unparsable version stays silent (can't confirm staleness — fail-soft).
|
|
34
|
+
*/
|
|
35
|
+
import { hostEnv } from './hostEnv.js';
|
|
36
|
+
/** Settings-lane env knobs (settings.json `env` block → 1a-envseed → process.env). */
|
|
37
|
+
export const HEADLESS_DEADLINE_ENV = 'SEMA_HEADLESS_DEADLINE_SEC';
|
|
38
|
+
export const HEADLESS_MAX_TURNS_ENV = 'SEMA_HEADLESS_MAX_TURNS';
|
|
39
|
+
/** Integer domains (inclusive). timeoutSec: 30s..24h; maxTurns: 1..1000. */
|
|
40
|
+
export const DEADLINE_SEC_MIN = 30;
|
|
41
|
+
export const DEADLINE_SEC_MAX = 86_400;
|
|
42
|
+
export const MAX_TURNS_MIN = 1;
|
|
43
|
+
export const MAX_TURNS_MAX = 1_000;
|
|
44
|
+
/** First server version whose /v1/tasks|/v1/tasks/stream accept body.limits (board [857]). */
|
|
45
|
+
export const LIMITS_WIRE_MIN_ENGINE = [1, 196, 0];
|
|
46
|
+
const DEADLINE_USAGE = `sema: --deadline expects a whole number of seconds between ${DEADLINE_SEC_MIN} and ${DEADLINE_SEC_MAX}.\n` +
|
|
47
|
+
' --deadline <sec> soft wall-clock budget for this run (sema superset — no upstream equivalent):\n' +
|
|
48
|
+
' the engine paces itself (nudges + call caps) and finalizes gracefully near the deadline\n' +
|
|
49
|
+
' (no flag) no deadline — today\'s behaviour';
|
|
50
|
+
const MAX_TURNS_USAGE = `sema: --max-turns expects a whole number of turns between ${MAX_TURNS_MIN} and ${MAX_TURNS_MAX}.\n` +
|
|
51
|
+
' --max-turns <n> maximum number of agentic turns for this run (upstream-aligned flag,\n' +
|
|
52
|
+
' enforced engine-side on the sema seam)\n' +
|
|
53
|
+
' (no flag) no turn cap — today\'s behaviour';
|
|
54
|
+
/** STRICT integer shape for explicit flag values: ASCII digits only (no sign/decimal/exponent/space). */
|
|
55
|
+
const INT_RE = /^\d+$/;
|
|
56
|
+
/**
|
|
57
|
+
* Pull the LAST value of a `--name <v>` / `--name=<v>` flag out of an argv slice (everything before a
|
|
58
|
+
* bare `--` — positionals are never flags; scenarioWire 纪律). Returns:
|
|
59
|
+
* {present:false} — flag absent
|
|
60
|
+
* {present:true, raw} — flag present with a value token
|
|
61
|
+
* {present:true, raw:undefined} — flag present but the value is missing/flag-shaped (fail-loud at caller)
|
|
62
|
+
*/
|
|
63
|
+
function lastFlagValue(argv, name) {
|
|
64
|
+
const eq = `${name}=`;
|
|
65
|
+
let present = false;
|
|
66
|
+
let raw;
|
|
67
|
+
let valueMissing = false;
|
|
68
|
+
for (let i = 0; i < argv.length; i++) {
|
|
69
|
+
const a = argv[i];
|
|
70
|
+
if (a === '--')
|
|
71
|
+
break; // positionals — never flags
|
|
72
|
+
if (a === name) {
|
|
73
|
+
present = true;
|
|
74
|
+
const nxt = argv[i + 1];
|
|
75
|
+
if (nxt === undefined || nxt.startsWith('-')) {
|
|
76
|
+
// note: a NEGATIVE number would look flag-shaped too — the domains are all-positive, so the
|
|
77
|
+
// fail-loud usage hint is the right answer for `--deadline -5` as well.
|
|
78
|
+
valueMissing = true;
|
|
79
|
+
raw = undefined;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
valueMissing = false;
|
|
83
|
+
raw = nxt;
|
|
84
|
+
i++; // consume the value token
|
|
85
|
+
}
|
|
86
|
+
else if (a.startsWith(eq)) {
|
|
87
|
+
present = true;
|
|
88
|
+
valueMissing = false;
|
|
89
|
+
raw = a.slice(eq.length);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (present && (valueMissing || raw === undefined))
|
|
93
|
+
return { present: true };
|
|
94
|
+
return present ? { present: true, raw } : { present: false };
|
|
95
|
+
}
|
|
96
|
+
/** Validate a RAW flag value against an inclusive integer domain. Returns the number or null. */
|
|
97
|
+
function parseIntInDomain(raw, min, max) {
|
|
98
|
+
const v = raw.trim();
|
|
99
|
+
if (!INT_RE.test(v))
|
|
100
|
+
return null;
|
|
101
|
+
const n = Number(v);
|
|
102
|
+
if (!Number.isSafeInteger(n) || n < min || n > max)
|
|
103
|
+
return null;
|
|
104
|
+
return n;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Parse `--deadline` + `--max-turns` out of an argv slice. No flags ⇒ `{ok:true}` with no limits.
|
|
108
|
+
* Repeated flag ⇒ LAST wins (commander convention). Missing value / non-integer / out-of-domain ⇒
|
|
109
|
+
* fail-LOUD parse error (never a silent default — same stance as `--scenario`/`--sandbox`).
|
|
110
|
+
* `flaggedFlags` names the flags that EXPLICITLY contributed (the version-gate probe trigger).
|
|
111
|
+
*/
|
|
112
|
+
export function parseLimitsArgv(argv) {
|
|
113
|
+
const limits = {};
|
|
114
|
+
const flaggedFlags = [];
|
|
115
|
+
const dl = lastFlagValue(argv, '--deadline');
|
|
116
|
+
if (dl.present) {
|
|
117
|
+
const n = dl.raw !== undefined ? parseIntInDomain(dl.raw, DEADLINE_SEC_MIN, DEADLINE_SEC_MAX) : null;
|
|
118
|
+
if (n === null)
|
|
119
|
+
return { ok: false, error: DEADLINE_USAGE };
|
|
120
|
+
limits.timeoutSec = n;
|
|
121
|
+
flaggedFlags.push('--deadline');
|
|
122
|
+
}
|
|
123
|
+
const mt = lastFlagValue(argv, '--max-turns');
|
|
124
|
+
if (mt.present) {
|
|
125
|
+
const n = mt.raw !== undefined ? parseIntInDomain(mt.raw, MAX_TURNS_MIN, MAX_TURNS_MAX) : null;
|
|
126
|
+
if (n === null)
|
|
127
|
+
return { ok: false, error: MAX_TURNS_USAGE };
|
|
128
|
+
limits.maxTurns = n;
|
|
129
|
+
flaggedFlags.push('--max-turns');
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
ok: true,
|
|
133
|
+
...(flaggedFlags.length > 0 ? { limits } : {}),
|
|
134
|
+
flaggedFlags,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* The settings-lane defaults. SILENT degrade on a bad value (batch-E triage shape — stricter than
|
|
139
|
+
* scenarioWire's warn-and-ignore: these are pacing hints, and a settings typo must neither brick nor
|
|
140
|
+
* spam every headless run). SEMA_DEBUG surfaces the drop.
|
|
141
|
+
*/
|
|
142
|
+
export function headlessLimitsFromEnv(env = hostEnv()) {
|
|
143
|
+
const out = {};
|
|
144
|
+
const dl = env[HEADLESS_DEADLINE_ENV]?.trim();
|
|
145
|
+
if (dl) {
|
|
146
|
+
const n = parseIntInDomain(dl, DEADLINE_SEC_MIN, DEADLINE_SEC_MAX);
|
|
147
|
+
if (n !== null)
|
|
148
|
+
out.timeoutSec = n;
|
|
149
|
+
else if (env.SEMA_DEBUG) {
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.error(`[sema] ${HEADLESS_DEADLINE_ENV}="${dl}" is not an integer in ${DEADLINE_SEC_MIN}..${DEADLINE_SEC_MAX} — ignoring the settings default`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const mt = env[HEADLESS_MAX_TURNS_ENV]?.trim();
|
|
155
|
+
if (mt) {
|
|
156
|
+
const n = parseIntInDomain(mt, MAX_TURNS_MIN, MAX_TURNS_MAX);
|
|
157
|
+
if (n !== null)
|
|
158
|
+
out.maxTurns = n;
|
|
159
|
+
else if (env.SEMA_DEBUG) {
|
|
160
|
+
// eslint-disable-next-line no-console
|
|
161
|
+
console.error(`[sema] ${HEADLESS_MAX_TURNS_ENV}="${mt}" is not an integer in ${MAX_TURNS_MIN}..${MAX_TURNS_MAX} — ignoring the settings default`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return out.timeoutSec !== undefined || out.maxTurns !== undefined ? out : undefined;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve the limits for a headless `-p` submit: explicit flags > settings env defaults > none
|
|
168
|
+
* (= no stamp, today's behaviour). Merge is PER-KNOB: `--deadline 300` + SEMA_HEADLESS_MAX_TURNS=50
|
|
169
|
+
* yields {timeoutSec:300, maxTurns:50}. Flag parse errors propagate fail-loud; the caller exits.
|
|
170
|
+
*/
|
|
171
|
+
export function limitsForPrint(argv, env = hostEnv()) {
|
|
172
|
+
const parsed = parseLimitsArgv(argv);
|
|
173
|
+
if (!parsed.ok)
|
|
174
|
+
return parsed;
|
|
175
|
+
const fromEnv = headlessLimitsFromEnv(env);
|
|
176
|
+
const merged = { ...(fromEnv ?? {}), ...(parsed.limits ?? {}) };
|
|
177
|
+
const any = merged.timeoutSec !== undefined || merged.maxTurns !== undefined;
|
|
178
|
+
return { ok: true, ...(any ? { limits: merged } : {}), flaggedFlags: parsed.flaggedFlags };
|
|
179
|
+
}
|
|
180
|
+
/** True when the engine version string satisfies the limits-wire minimum (≥1.196.0). Unparsable ⇒ false. */
|
|
181
|
+
export function versionSupportsLimits(v) {
|
|
182
|
+
const m = v ? /^(\d+)\.(\d+)\.(\d+)/.exec(v) : null;
|
|
183
|
+
if (!m)
|
|
184
|
+
return false;
|
|
185
|
+
const [a, b, c] = [Number(m[1]), Number(m[2]), Number(m[3])];
|
|
186
|
+
const [ma, mb, mc] = LIMITS_WIRE_MIN_ENGINE;
|
|
187
|
+
if (a !== ma)
|
|
188
|
+
return a > ma;
|
|
189
|
+
if (b !== mb)
|
|
190
|
+
return b > mb;
|
|
191
|
+
return c >= mc;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Version-gate probe (triage risk ③): GET `${baseUrl}/health`, read `version`, and return ONE honest
|
|
195
|
+
* stderr line when the engine is confirmed <1.196.0 — the caller prints it and SENDS ANYWAY (the old
|
|
196
|
+
* server drops the field server-side; warn, don't block). Returns undefined when the engine is new
|
|
197
|
+
* enough, unreachable, or self-describes no parsable version (can't confirm staleness — fail-soft
|
|
198
|
+
* silent; the release-pinned self-spawn lane never even reaches this probe).
|
|
199
|
+
*/
|
|
200
|
+
export async function engineLimitsSupportWarning(baseUrl, flaggedFlags, opts) {
|
|
201
|
+
const fetchImpl = opts?.fetchImpl ?? fetch;
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
const timer = setTimeout(() => controller.abort(), opts?.timeoutMs ?? 1500);
|
|
204
|
+
try {
|
|
205
|
+
const res = await fetchImpl(`${baseUrl.replace(/\/+$/, '')}/health`, {
|
|
206
|
+
method: 'GET',
|
|
207
|
+
signal: controller.signal,
|
|
208
|
+
});
|
|
209
|
+
if (!res.ok)
|
|
210
|
+
return undefined;
|
|
211
|
+
const body = (await res.json());
|
|
212
|
+
const version = typeof body?.version === 'string' ? body.version : undefined;
|
|
213
|
+
// No version field = pre-1.96 engine — definitely older than 1.196.0, warn honestly.
|
|
214
|
+
if (versionSupportsLimits(version))
|
|
215
|
+
return undefined;
|
|
216
|
+
const flags = flaggedFlags.length > 0 ? flaggedFlags.join('/') : '--deadline/--max-turns';
|
|
217
|
+
return `[sema] engine ${version ?? '(unversioned, pre-1.96)'} does not support ${flags} (needs ≥1.196.0); the flag will be ignored by the engine`;
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return undefined; // unreachable/timeout — cannot confirm staleness, stay silent
|
|
221
|
+
}
|
|
222
|
+
finally {
|
|
223
|
+
clearTimeout(timer);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -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;
|