@sema-agent/client-core 0.64.1 → 0.65.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/CHANGELOG.md +226 -0
- package/README.md +15 -7
- package/dist/adapt/arms.js +125 -7
- package/dist/adapt/ids.d.ts +22 -0
- package/dist/adapt/ids.js +29 -0
- package/dist/adapt/panelTasks.d.ts +22 -0
- package/dist/adapt/panelTasks.js +45 -0
- package/dist/adapt/textStream.js +6 -3
- package/dist/adapt.d.ts +1 -1
- package/dist/adapt.js +3 -0
- package/dist/adapter/downstream/eventToSdkMessage.d.ts +0 -10
- package/dist/adapter/downstream/eventToSdkMessage.js +207 -65
- package/dist/adapter/downstream/terminalToSdkResult.d.ts +4 -4
- package/dist/adapter/downstream/terminalToSdkResult.js +43 -12
- package/dist/adapter/downstream/turnUsageToModelUsage.d.ts +23 -2
- package/dist/adapter/downstream/turnUsageToModelUsage.js +7 -1
- package/dist/adapter/runStream.js +39 -2
- package/dist/adapter/types.d.ts +12 -0
- package/dist/autoModeUnavailable.d.ts +48 -79
- package/dist/autoModeUnavailable.js +70 -99
- package/dist/classifierStatus.d.ts +25 -71
- package/dist/classifierStatus.js +110 -105
- package/dist/cloudConfigWireCaps.js +16 -0
- package/dist/decideReceipt.d.ts +117 -0
- package/dist/decideReceipt.js +142 -0
- package/dist/engineErrorCodes.d.ts +32 -0
- package/dist/engineErrorCodes.js +42 -0
- package/dist/fleet/fleetProjection.d.ts +24 -1
- package/dist/fleet/fleetProjection.js +26 -1
- package/dist/fleetAgentPanelProjection.js +6 -1
- package/dist/gateVocabulary.d.ts +9 -1
- package/dist/gateVocabulary.js +46 -4
- package/dist/hitl/askGateWire.js +22 -1
- package/dist/hitl/gateLedger.d.ts +24 -0
- package/dist/hitl/gateLedger.js +8 -0
- package/dist/hitl/hitlBridge.js +14 -2
- package/dist/hitl/parkResolver.d.ts +23 -2
- package/dist/hitl/parkResolver.js +34 -6
- package/dist/hitl/toolApprovalWire.d.ts +94 -6
- package/dist/hitl/toolApprovalWire.js +127 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -6
- package/dist/mcpWireCaps.d.ts +31 -0
- package/dist/mcpWireCaps.js +12 -0
- package/dist/notifications.js +11 -2
- package/dist/runTerminal.d.ts +48 -0
- package/dist/runTerminal.js +59 -0
- package/dist/seam.d.ts +131 -1
- package/dist/seam.js +22 -0
- package/docs/INTEGRATION-CLIENTS.md +934 -61
- package/package.json +2 -2
|
@@ -2,10 +2,9 @@ import { stamp, } from '../types.js';
|
|
|
2
2
|
import { turnUsageToModelUsage } from './turnUsageToModelUsage.js';
|
|
3
3
|
// 0.60.0(engine ≥7.64.0 / sdk 8.4.0):`tool_end.gate` 的三端共用窄读器(判定归包,端不许自拼)。
|
|
4
4
|
import { gateOutcomeOf } from '../../gateOutcome.js';
|
|
5
|
-
// 0.
|
|
6
|
-
// `
|
|
7
|
-
//
|
|
8
|
-
import { classifierBreakerOf } from '../../autoModeUnavailable.js';
|
|
5
|
+
// 0.65.0(core 7.12.0 CHANGELOG「Removed (BREAKING)」`WiringManifest.autoMode.breaker`):
|
|
6
|
+
// 熔断族整只退役 ⇒ 本臂此前对 `autoMode.breaker` 的窄读(`classifierBreakerOf`)与它的型位一并
|
|
7
|
+
// 删除(clean-cut,不留兼容读)。`autoMode` 段剩下的两位 `armed` / `reason` 一个字节不动。
|
|
9
8
|
// #51: `stamp<T extends { uuid?: string; session_id?: string }>` — passing an
|
|
10
9
|
// arm-body object literal straight to `stamp` triggers a TS quirk: the
|
|
11
10
|
// literal gets excess-property-checked against the CONSTRAINT (which only
|
|
@@ -56,14 +55,51 @@ export const INTERNAL_SDK_ARM_TYPES = new Set([
|
|
|
56
55
|
// 超集投影(`_sema_modelGate` / `_sema_autoMode`)。整份 manifest 的其余段仍不在本切片里 ——
|
|
57
56
|
// 射程写在 `case 'wiring_manifest'` 头注,别读成「manifest 接上了」。
|
|
58
57
|
'wiring_manifest',
|
|
58
|
+
// B-078 / L-208(0.65.0;design/172 流内审批协议):呈卡 / 撕卡两帧的内部臂。
|
|
59
|
+
// 载荷是**信封原样**(不窄化到 v1,不摘键重铸)——理由见两个 `case` 的头注。
|
|
60
|
+
'approval_request',
|
|
61
|
+
'approval_revoke',
|
|
62
|
+
// B-072 ④(0.65.0):`message_committed` 会话条目落账帧 —— 从「本切片不投影」改为中性内部臂
|
|
63
|
+
// (chrome 记账,绝不铸 transcript 行)。理由见 `case 'message_committed'` 头注:它是
|
|
64
|
+
// `compacted.preserved_segment` 的**唯一**解析腿。
|
|
65
|
+
'message_committed',
|
|
59
66
|
]);
|
|
60
|
-
/**
|
|
61
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Wrap neutral content blocks in the CC `assistant` message envelope.
|
|
69
|
+
*
|
|
70
|
+
* ── L-215③(0.65.0):`message` 上补两个**身份**键 ─────────────────────────────────────────
|
|
71
|
+
* 此前交给渲染端的是**裸** `{role, content}` —— CC 的 `SDKAssistantMessage.message` 上那四个键
|
|
72
|
+
* (`id` / `model` / `usage` / `stop_reason`)一个都没有,于是宿主十几处消费面只能各自**估算**。
|
|
73
|
+
* 本批补**两个**,另外两个**刻意不补**,理由逐条如下(不是遗漏,别下一棒又来补一遍):
|
|
74
|
+
*
|
|
75
|
+
* · **`id` ⇐ wire 的 `eventId`**(core 铸的 uuidv7,§E2)。取它而不是现铸,是因为它**确定性**:
|
|
76
|
+
* 同一条消息在 live 与 durable replay 两条腿上必须同 id,否则宿主的去重与 R8 rewind 会把一次
|
|
77
|
+
* 重放当成一条新消息。wire 没给 ⇒ **键不铸**(诚实缺席),绝不塞一个随机 v4 冒充身份。
|
|
78
|
+
* ⚠️ 与信封自己的 `uuid` 是**两个**身份位:后者是本地渲染键(`stamp` 现铸),前者是引擎身份。
|
|
79
|
+
* · **`model` ⇐ `ctx.model`**(宿主开流时钉的那一个,契约见 {@link EmitContext.model})。
|
|
80
|
+
* 缺席 ⇒ 键不铸,**绝不猜**。
|
|
81
|
+
* · 🔴 **`usage` 不铸,理由是帧序**:wire 上 `text`/`text_end` 这些内容臂**先**到、
|
|
82
|
+
* `turn_end.usage` **后**到 —— 臂发出的那一刻,引擎还没说这一轮花了多少。在这里铸一个数只能是
|
|
83
|
+
* **估算**,而估算正是本件要根治的东西(cli 1.0.107 的壳侧回落补丁就是它的症状)。
|
|
84
|
+
* 真值走 `turn_usage` 臂的 `_sema_last_assistant_usage` 超集键(见 runStream 的 turn_end 折叠点)。
|
|
85
|
+
* ⚠️ 也**不**采「turn_end 到达时补发一条 assistant 更新」的形:CC 的消息面没有这种形,
|
|
86
|
+
* 合成一条会让宿主的转录出现一条引擎从没发过的消息。
|
|
87
|
+
* · 🔴 **`stop_reason` 同理不铸**:`turn_end.stopReason` 与 usage 同帧,同一条帧序结论;
|
|
88
|
+
* 它走 `turn_usage` 臂的 `_sema_stop_reason`。
|
|
89
|
+
*/
|
|
90
|
+
function assistantArm(ctx, content, eventId) {
|
|
91
|
+
const id = typeof eventId === 'string' && eventId.length > 0 ? eventId : undefined;
|
|
92
|
+
const model = typeof ctx.model === 'string' && ctx.model.length > 0 ? ctx.model : undefined;
|
|
62
93
|
return stamp(ctx, armBody({
|
|
63
94
|
type: 'assistant',
|
|
64
95
|
// `message` is coreSchemas APIAssistantMessagePlaceholder = z.unknown(): we
|
|
65
96
|
// hand the renderer a neutral { role, content } it can read, not the raw wire.
|
|
66
|
-
message: {
|
|
97
|
+
message: {
|
|
98
|
+
role: 'assistant',
|
|
99
|
+
content,
|
|
100
|
+
...(id !== undefined ? { id } : {}),
|
|
101
|
+
...(model !== undefined ? { model } : {}),
|
|
102
|
+
},
|
|
67
103
|
parent_tool_use_id: null,
|
|
68
104
|
}));
|
|
69
105
|
}
|
|
@@ -132,6 +168,43 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
132
168
|
// 比本包类型新一版,类型收窄不构成「字段一定合法」的保证。
|
|
133
169
|
case 'human_input':
|
|
134
170
|
return humanInputProjection(ev, ctx);
|
|
171
|
+
/**
|
|
172
|
+
* B-072 ④(0.65.0;core message-identity Phase 1 / server ≥7.69.0 live 腿真发)——
|
|
173
|
+
* 一条**可渲染消息**的会话树条目已落账。此前它落在 `not_in_slice`,理由写着「本切片无对位
|
|
174
|
+
* 渲染物」;那句话对**转录行**成立,对**定位**不成立:
|
|
175
|
+
* · core 的 `compacted.preserved_segment.firstKeptEntryId` 是一个 **entryId**,
|
|
176
|
+
* · core 同一段逐字说消费方**用本帧**自建 `entryId → message` 映射来解析它,
|
|
177
|
+
* ⇒ 只投 `preserved_segment` 而把本帧丢掉,等于把一个**解析不了的锚**交给端。
|
|
178
|
+
*
|
|
179
|
+
* 处置与同族 `human_input` 逐条相同:**中性内部臂**(不是 transcript 行 —— 本帧不带正文,
|
|
180
|
+
* 铸一行就是替引擎编一条它没说过的话),由 `adapt` 的臂转成 chrome 记账事件。
|
|
181
|
+
*
|
|
182
|
+
* 🔴 **两个必填位任一读不出 ⇒ `malformed` 留痕**,绝不铸半个账本行:一个空 `entryId` 落进端的
|
|
183
|
+
* 映射表会静默匹配掉一条真消息(比没有这一帧更坏)。`role` 按**开集**读(core 今天三词,
|
|
184
|
+
* 上游加第四词照过);`toolCallId` 只在 `role==='toolResult'` 腿上有,缺席不铸。
|
|
185
|
+
* ⚠️ **投递腿只有 live**(server 7.69.0 亲读):durable/bg 腿把本帧喂给了 rewind 锚而**不**入
|
|
186
|
+
* 账本 ⇒ 端在 replay/resume 上看不到它。这是上游的投递事实,不是本包的剥离 —— 端据此
|
|
187
|
+
* 建的映射表在 resume 腿上会是空的,`_sema_preserved_segment` 那一格因此要按「解析不出
|
|
188
|
+
* 就不画分割线」处理,不许回退成「画在最后一条」。
|
|
189
|
+
*/
|
|
190
|
+
case 'message_committed': {
|
|
191
|
+
const entryId = typeof ev.entryId === 'string' && ev.entryId.length > 0 ? ev.entryId : undefined;
|
|
192
|
+
const role = typeof ev.role === 'string' && ev.role.length > 0 ? ev.role : undefined;
|
|
193
|
+
if (entryId === undefined || role === undefined)
|
|
194
|
+
return dropped('malformed', 'message_committed');
|
|
195
|
+
const toolCallId = typeof ev.toolCallId === 'string' && ev.toolCallId.length > 0 ? ev.toolCallId : undefined;
|
|
196
|
+
return projected(stamp(ctx, armBody({
|
|
197
|
+
type: 'message_committed',
|
|
198
|
+
entryId,
|
|
199
|
+
role,
|
|
200
|
+
...(toolCallId !== undefined ? { toolCallId } : {}),
|
|
201
|
+
// §E2 身份:durable 重放同一条账本帧必须落成同一笔(同 human_input 的 eventId 纪律)。
|
|
202
|
+
...(typeof ev.eventId === 'string' && ev.eventId.length > 0 ? { eventId: ev.eventId } : {}),
|
|
203
|
+
...(typeof ev.parentToolCallId === 'string' && ev.parentToolCallId.length > 0
|
|
204
|
+
? { parentToolCallId: ev.parentToolCallId }
|
|
205
|
+
: {}),
|
|
206
|
+
})));
|
|
207
|
+
}
|
|
135
208
|
// service 1.75 — `workflow_complete`: an out-of-band background-workflow completion push, ridden onto
|
|
136
209
|
// the session's next stream open (BEFORE the run's own events). Projects to a NEUTRAL internal arm;
|
|
137
210
|
// the upstream bridge turns it into CC's `<task-notification>` injection.
|
|
@@ -164,10 +237,10 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
164
237
|
}
|
|
165
238
|
// CS-1 §2.2 — turn-aggregated answer text (durable). Drop raw block array.
|
|
166
239
|
case 'text':
|
|
167
|
-
return projected(assistantArm(ctx, [{ type: 'text', text: ev.text }]));
|
|
240
|
+
return projected(assistantArm(ctx, [{ type: 'text', text: ev.text }], ev.eventId));
|
|
168
241
|
// CS-3 §2.1 — turn-aggregated thinking (durable). Distinct collapsible block.
|
|
169
242
|
case 'reasoning':
|
|
170
|
-
return projected(assistantArm(ctx, [{ type: 'thinking', thinking: ev.text }]));
|
|
243
|
+
return projected(assistantArm(ctx, [{ type: 'thinking', thinking: ev.text }], ev.eventId));
|
|
171
244
|
// CS-2 §2.4 — per-token answer fragment (live only). Typewriter append.
|
|
172
245
|
case 'text_delta':
|
|
173
246
|
return projected(streamEventArm(ctx, { type: 'text_delta', delta: ev.delta }));
|
|
@@ -198,19 +271,56 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
198
271
|
? { parentToolCallId: ev.parentToolCallId }
|
|
199
272
|
: {}),
|
|
200
273
|
},
|
|
201
|
-
]));
|
|
202
|
-
// CS-8 §2.8 — compaction divider. tokensBefore ⇐ pre_tokens.
|
|
203
|
-
// engine sends it (core 1.156 manual verb → "manual"; absent ⇒ 'auto'). core 1.219
|
|
274
|
+
], ev.eventId));
|
|
275
|
+
// CS-8 §2.8 — compaction divider. tokensBefore ⇐ pre_tokens. core 1.219
|
|
204
276
|
// (attachWorkingFiles default ON): `attachedFiles:[{path,chars,truncated}]` rides through raw so
|
|
205
277
|
// the bridge can project the CC post-compact "Read {path}" attachment rows (blackboard ask ②).
|
|
278
|
+
//
|
|
279
|
+
// 🔴 **B-072 ①(0.65.0):`trigger` 由三词折叠改为开集透传**。修前这一格逐字是
|
|
280
|
+
// `ev.trigger === 'manual' ? 'manual' : 'auto'` —— 一张**闭集折叠表**,于是 core 5.16.0 起
|
|
281
|
+
// 真发的第三个值 `forced`(prompt-too-long 恢复腿 / trim-pressure 腿:压缩**不是可选的**)
|
|
282
|
+
// 到了包边界被改写成 `auto`。core 的臂注逐字点名这件事:wire 现在带的是**钩子层看见的
|
|
283
|
+
// 那个值**(`the SAME value the hooks saw instead of flattening it to "auto"`)——
|
|
284
|
+
// 上游刚刚停止做的折叠,包在下游又做了一遍,这正是 B-025 家族的形。
|
|
285
|
+
// ⚠️ **不是破坏性变更**:core 同一句写明 `Additive value space — a consumer switching on
|
|
286
|
+
// auto/manual keeps its exact old behavior`,CC 消费者按 `!== 'manual'` 分支的行为逐字不变。
|
|
287
|
+
// 缺席 / 空串 / 非串仍回落 `'auto'`(sdk 臂注逐字「缺席或未知值一律按 auto 渲染」)——
|
|
288
|
+
// 回落的是**读不出来**那一档,不是「读出来了但不认识」那一档。
|
|
289
|
+
// 🔴 **两个超集键**(`_sema_` 前缀,CC 同名键语义零改;缺席一律不铸):
|
|
290
|
+
// · `_sema_preserved_segment` ⇐ core `preserved_segment`(CC `SDKCompactBoundary` parity 位)
|
|
291
|
+
// —— 保留尾段的起点 entryId,端拿它把压缩分割线画在**那条消息之前**并重新接上尾段。
|
|
292
|
+
// 解析腿 = 本文件同批新投的 `message_committed` 臂(core 亲口:消费方用它自建
|
|
293
|
+
// `entryId → message` 映射)。半个锚不铸:载体在而 id 读不出 ⇒ 整键不出现。
|
|
294
|
+
// · `_sema_clamped_ratio` ⇐ core `clampedRatio`(design/145 §3)—— 这次压缩的摘要**输入**
|
|
295
|
+
// 被钳掉了多大比例。core 逐字 `bounded loss, disclosed either way` ⇒ 它是**披露**位,
|
|
296
|
+
// 真 0(「一点没钳」)是一条事实,**不是**缺席;缺席(旧引擎 / 没发生钳)则键不铸。
|
|
297
|
+
// 记账:server 7.69.0 `trace/project.js` 白名单真发这一键,而 sdk 8.8.0 的 `compacted`
|
|
298
|
+
// 臂上**还没有座位** ⇒ 这里是防御 raw 读(同 `label` / `model` 的既有姿势)。退役条件 =
|
|
299
|
+
// sdk 补上该位的当天,`run-compaction-boundary-projection-test.mjs` F10 行当场红逼复核。
|
|
206
300
|
case 'compacted': {
|
|
207
|
-
const
|
|
301
|
+
const raw = ev;
|
|
302
|
+
const attachedFiles = raw.attachedFiles;
|
|
303
|
+
const seg = raw.preserved_segment;
|
|
304
|
+
const firstKeptEntryId = typeof seg === 'object' && seg !== null && !Array.isArray(seg)
|
|
305
|
+
? seg.firstKeptEntryId
|
|
306
|
+
: undefined;
|
|
307
|
+
const clampedRatio = raw.clampedRatio;
|
|
208
308
|
return projected(stamp(ctx, armBody({
|
|
209
309
|
type: 'system',
|
|
210
310
|
subtype: 'compact_boundary',
|
|
211
311
|
compact_metadata: {
|
|
212
|
-
|
|
312
|
+
// 开集透传:读得出的词原样过境;读不出(缺席/空串/非串)才回落 CC 的 'auto'。
|
|
313
|
+
// 🔴 **不写 `as 'auto' | 'manual'`**(修前那条 cast 现在会变成谎言):这一格的运行期值
|
|
314
|
+
// 域已经是开集,断言成两个字面量 = 型面对下一棒撒谎([cast-lies-need-a-gate-not-a-rule])。
|
|
315
|
+
// 留 `string` 即可 —— 本臂体最终仍走 `as unknown as SDKMessage`,没有任何约束要求它窄。
|
|
316
|
+
trigger: typeof ev.trigger === 'string' && ev.trigger.length > 0 ? ev.trigger : 'auto',
|
|
213
317
|
pre_tokens: ev.tokensBefore ?? 0,
|
|
318
|
+
...(typeof firstKeptEntryId === 'string' && firstKeptEntryId.length > 0
|
|
319
|
+
? { _sema_preserved_segment: { firstKeptEntryId } }
|
|
320
|
+
: {}),
|
|
321
|
+
...(typeof clampedRatio === 'number' && Number.isFinite(clampedRatio)
|
|
322
|
+
? { _sema_clamped_ratio: clampedRatio }
|
|
323
|
+
: {}),
|
|
214
324
|
},
|
|
215
325
|
...(Array.isArray(attachedFiles) && attachedFiles.length > 0 ? { attachedFiles } : {}),
|
|
216
326
|
})));
|
|
@@ -459,11 +569,23 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
459
569
|
// 🔴 条件 spread 与 server 投影**同形同条件**:`string` 且非空才带。空串既不是模型 id 也不是
|
|
460
570
|
// 「不知道」,透下去只会在面板上渲成一个空模型名([honest-absence-not-fabricated-zero])。
|
|
461
571
|
const model = ev.model;
|
|
572
|
+
// 🔴 B-074(0.65.0)—— `status`:这一格是**行的终态信号**,而修前这张白名单里没有它,于是
|
|
573
|
+
// core 明写会发的那一拍(`ONE final tick fires at the sub-run's settle with the terminal
|
|
574
|
+
// face`)到了宿主手上与一次普通 running 拍**逐字节同形** ⇒ 面板行只能等防御 sweep 或一条
|
|
575
|
+
// 通知帧来收尸(core 那句话的下半段正是「so a uuid-lane consumer sees the row finish
|
|
576
|
+
// instead of a bare removal after the last running beat」)。与 `model` / `workflowRunId`
|
|
577
|
+
// 是**同一病形的第 N 例**:事实已经在 wire 上,而包边界这一格空转。
|
|
578
|
+
// server `taskProgressEventData` 白名单逐字 `...(ev.status !== undefined ? { status: ev.status } : {})`。
|
|
579
|
+
// **开集读**:core 今天三词、sdk 已标 `| (string & {})`;词表属主在 core,包只透传不裁决
|
|
580
|
+
// (谁是终态由 adapt 层按 core 明写的两个词判,见 `arms.ts` 的 `TERMINAL_TICK_STATUSES`)。
|
|
581
|
+
// 空串/非串不铸 —— 与本臂其余条件 spread 同一判据(空串既不是词也不是「不知道」)。
|
|
582
|
+
const status = ev.status;
|
|
462
583
|
return projected(stamp(ctx, armBody({
|
|
463
584
|
type: 'task_progress',
|
|
464
585
|
taskId: ev.taskId,
|
|
465
586
|
...(ev.name !== undefined ? { name: ev.name } : {}),
|
|
466
587
|
...(ev.usage !== undefined ? { usage: ev.usage } : {}),
|
|
588
|
+
...(typeof status === 'string' && status.length > 0 ? { status } : {}),
|
|
467
589
|
...(typeof model === 'string' && model.length > 0 ? { model } : {}),
|
|
468
590
|
...(typeof currentAction === 'string' && currentAction.length > 0
|
|
469
591
|
? { currentAction }
|
|
@@ -660,15 +782,16 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
660
782
|
// `meta`:流首帧的 taskId/sessionId 一等信号 —— 身份面由 SDK/传输层自己消费(runStream 只按
|
|
661
783
|
// `eventSeq` 去重),没有可渲染内容。
|
|
662
784
|
case 'meta':
|
|
663
|
-
// `file_link` / `prompt_assembled` / `model_usage` / `context_usage` / `config_assembled
|
|
664
|
-
//
|
|
785
|
+
// `file_link` / `prompt_assembled` / `model_usage` / `context_usage` / `config_assembled`:
|
|
786
|
+
// 引擎的可观测/审计面帧,本切片(CC transcript 投影)无对位渲染物;
|
|
665
787
|
// 消费它们的是 fleet/审计面而不是会话转录。
|
|
788
|
+
// ⚠️ `message_committed` **已在 0.65.0 搬出本档**(B-072 ④):它不是「没有对位物」——
|
|
789
|
+
// 它是 `compacted.preserved_segment` 的唯一解析腿。新臂见本文件 `case 'message_committed'`。
|
|
666
790
|
case 'file_link':
|
|
667
791
|
case 'prompt_assembled':
|
|
668
792
|
case 'model_usage':
|
|
669
793
|
case 'context_usage':
|
|
670
794
|
case 'config_assembled':
|
|
671
|
-
case 'message_committed':
|
|
672
795
|
// `needs_review`:plan-review park 的**事件**形;壳消费的是 done{status:'needs_review'} 终帧
|
|
673
796
|
// (runStream 的 plan_review_park chrome 臂),事件形在本切片重复且更早,故不投影。
|
|
674
797
|
case 'needs_review':
|
|
@@ -690,48 +813,39 @@ export function eventToSdkMessage(ev, ctx) {
|
|
|
690
813
|
/**
|
|
691
814
|
* `approval_request`(design/172 流内审批协议,server ≥7.3.0、7.5.0 起默认开)。
|
|
692
815
|
*
|
|
693
|
-
* 🔴
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
*
|
|
700
|
-
*
|
|
701
|
-
*
|
|
702
|
-
*
|
|
703
|
-
*
|
|
704
|
-
* 📋 缺口如实记账(**不**在本批修 —— 那是一条新卡链,是功能不是修复):
|
|
705
|
-
* · 存量路径今天仍能决断:SDK 头注(tool-approvals.d.ts)载明开关打开时同一只 ask 出两帧、顺序
|
|
706
|
-
* 钉死「先 legacy `tool_approval`、后 `approval_request`」,两帧同带 `approvalId`,legacy 腿在。
|
|
707
|
-
* · 但**降级路径不保证**:legacy respond 是 live-only + same-replica(错副本 404),而重连时
|
|
708
|
-
* pending 卡是靠 `approval_request` preamble 对账的 —— 「收到 legacy 帧后断线、按 Last-Event-ID
|
|
709
|
-
* 重连只再看到 approval_request」以及非粘性多副本部署这两条路上,今天只能等窗口到期→park/deny。
|
|
710
|
-
* · 接法(留给专门的一车):`isApprovalRequestFrameV1` 校形 → 按 approvalId/askId 去重并处理重连
|
|
711
|
-
* preamble → 带幂等键的 `decideAsk` 持久 CAS → 仅在能力位关闭/新帧不可用时回落 legacy respond。
|
|
712
|
-
* 在那条链落地之前,**不许**由本切片顺手编一个假 transcript 形来假装接上了。
|
|
816
|
+
* 🔴 **0.65.0(B-078 / L-208)起真接上了**:本臂此前是 `dropped('unsupported_arm')` —— 有痕,
|
|
817
|
+
* 但**包内零消费口**,于是壳(cli)在自己的 `approvalStreamWire.ts` 里单独接了一份,而
|
|
818
|
+
* desktop / web 只走包管线 ⇒ 它们开流 preamble 恢复不了 pending 卡集。同一条 wire 上的同一
|
|
819
|
+
* 件事,一个端有、两个端没有,正是归层要根治的形。现改投 chrome 臂
|
|
820
|
+
* ({@link import('../../seam.js').ApprovalRequestChromeEvent}),消费义务写在那只接口的头注里。
|
|
821
|
+
* 🔴 **与 `tool_approval` / `tool_approval_complete` 仍不同源**(上面那两条走
|
|
822
|
+
* `isToolApprovalFrame` + `hitl/frameRouter` 的审批卡链,本臂走 chrome 平面):开关打开时同一
|
|
823
|
+
* 只 ask 出两帧、顺序钉死「先 legacy、后本帧」、两帧同带 `approvalId` ⇒ **去重是宿主的动作**
|
|
824
|
+
* (它才有卡集状态),库不在这一层替它做。
|
|
825
|
+
* 🔴 载荷**不窄化到 v1**(sdk events.d.ts:961-968 的理由逐字搬在 `approvalFrameProjection` 头注)。
|
|
713
826
|
*/
|
|
714
|
-
case 'approval_request':
|
|
715
|
-
|
|
827
|
+
case 'approval_request': {
|
|
828
|
+
const body = approvalFrameBody(ev);
|
|
829
|
+
if (body === undefined)
|
|
830
|
+
return dropped('malformed', 'approval_request');
|
|
831
|
+
return projected(stamp(ctx, armBody({ type: 'approval_request', ...body })));
|
|
832
|
+
}
|
|
716
833
|
/**
|
|
717
|
-
* `approval_revoke`(design/172 撤卡帧;server ≥7.55.0
|
|
718
|
-
* `AgentEvent` union —— 0.58.0 sdk 8.2.0 提货批**编译期逼进来**的臂,与 `engine_notice`
|
|
719
|
-
* / `human_input` 走的是同一条路:臂一进 union,`default` 的 B5 穷举断言当场红)。
|
|
834
|
+
* `approval_revoke`(design/172 撤卡帧;[5924]/S-58,server ≥7.55.0 起也进 durable 账本重放)。
|
|
720
835
|
*
|
|
721
|
-
* 🔴 **与 `approval_request`
|
|
722
|
-
*
|
|
723
|
-
*
|
|
724
|
-
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
* `reportDroppedFrame` 走宿主 sink + console 留痕。撤卡帧带的是**用户可见的状态迁移**
|
|
728
|
-
* (那张卡已经不该再被决断了),悄悄丢掉它比吼一行更坏。
|
|
729
|
-
* 📋 缺口如实记账(**不**在本批修 —— 与 `approval_request` 是同一条新卡链,属功能不属修复):
|
|
730
|
-
* 接法在上一臂的「接法」段里写着,撤卡半场按 `approvalId` 撤同一张卡;在那条链落地之前
|
|
731
|
-
* **不许**由本切片顺手编一个假 transcript 形来假装接上了。
|
|
836
|
+
* 🔴 **与 `approval_request` 同源同处置**(载荷同是 `ApprovalFrameEnvelope` 信封),所以并在它
|
|
837
|
+
* 旁边 —— 它是同一条流内审批协议的**撤销**半场。0.65.0(B-078 / L-208)同批接上:此前
|
|
838
|
+
* `dropped('unsupported_arm')` ⇒ 只走包管线的宿主在撤销之后**本地卡不清**,用户对着一张
|
|
839
|
+
* 已经决断不了的卡按 Yes。现改投 chrome 臂
|
|
840
|
+
* ({@link import('../../seam.js').ApprovalRevokeChromeEvent}),三条消费纪律(按 `taskId` 归属 /
|
|
841
|
+
* 账本重放只渲时间线 / 已 DECIDED 的兄弟不在 `askIds` 里)写在那只接口的头注里。
|
|
732
842
|
*/
|
|
733
|
-
case 'approval_revoke':
|
|
734
|
-
|
|
843
|
+
case 'approval_revoke': {
|
|
844
|
+
const body = approvalFrameBody(ev);
|
|
845
|
+
if (body === undefined)
|
|
846
|
+
return dropped('malformed', 'approval_revoke');
|
|
847
|
+
return projected(stamp(ctx, armBody({ type: 'approval_revoke', ...body })));
|
|
848
|
+
}
|
|
735
849
|
// ── `engine_notice`(#310 / #318 件①,server ≥7.36,契约 = ASSISTANT-WIRE-CONTRACT 附录 D)────
|
|
736
850
|
// 🔴 **到期复核已兑现(sdk 7.4.0 提货,0.48.0)** —— 与 `human_input`(sdk 6.9.0)逐字同一条路。
|
|
737
851
|
// 本臂此前是 switch **之前**的一条 raw 预分派,理由 = 它还没进已发布 SDK 的 `AgentEvent`
|
|
@@ -908,14 +1022,9 @@ function projectModelGateSection(raw) {
|
|
|
908
1022
|
* 理由不是「重复上游」而是**非投影口**(宿主自建管线 / 重放存量转录)喂进来的帧不过 server ——
|
|
909
1023
|
* 一个 `{armed:true, reason:'settings_denied'}` 会让消费端同时读到「武装了」和「被拒了」。
|
|
910
1024
|
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
913
|
-
*
|
|
914
|
-
* · **缺席 = 老引擎**,不是「没熔断过」;不铸 `null`、不铸空对象;
|
|
915
|
-
* · **坏形只丢自己**:熔断记录读不出来时,`armed`/`reason` 那两格仍是真读数,照旧过帧
|
|
916
|
-
* (丢整段 = 把「这条腿的 auto 姿态」一起判没,而它与熔断记录是两件事);
|
|
917
|
-
* · 🔴 **绝不**给它与 `armed`/`reason` 补互证:合闩后的腿正是 `{armed:false, reason:"no_intent"}`,
|
|
918
|
-
* 补一条互证会把最常见的那一种真读数整段判没。
|
|
1025
|
+
* 🔴 **`breaker` 一位 0.65.0 退役**(core 7.12.0 删 `WiringManifest.autoMode.breaker`):本臂不再
|
|
1026
|
+
* 读它、不再投它。老引擎的帧上若仍带这个键,它落在本函数的**未读键**里被丢弃 —— 与本层对
|
|
1027
|
+
* 一切未声明键的处置逐字相同(投影是白名单,不是透传)。
|
|
919
1028
|
*/
|
|
920
1029
|
function projectAutoModeSection(raw) {
|
|
921
1030
|
if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
|
|
@@ -927,8 +1036,7 @@ function projectAutoModeSection(raw) {
|
|
|
927
1036
|
return undefined;
|
|
928
1037
|
if (armed !== (reason === 'armed'))
|
|
929
1038
|
return undefined;
|
|
930
|
-
|
|
931
|
-
return { armed, reason, ...(breaker !== undefined ? { breaker } : {}) };
|
|
1039
|
+
return { armed, reason };
|
|
932
1040
|
}
|
|
933
1041
|
/**
|
|
934
1042
|
* `wiring_manifest` 帧 → 两个超集键的**纯投影**(公面导出;三端共用,壳侧绝不自抄一份形校验)。
|
|
@@ -993,6 +1101,40 @@ function engineNoticeProjection(ev, ctx) {
|
|
|
993
1101
|
...(typeof ev.id === 'string' && ev.id.length > 0 ? { eventSeq: ev.id } : {}),
|
|
994
1102
|
})));
|
|
995
1103
|
}
|
|
1104
|
+
/**
|
|
1105
|
+
* `approval_request` / `approval_revoke` 的**信封级成形判**(B-078 / L-208,0.65.0;design/172)——
|
|
1106
|
+
* 两条 `case` 共用这一只(判据单源),而 `type` 字面量留在各自的 `case` 里:
|
|
1107
|
+
* 内部臂词表的常驻对账门(pure 门 A-004.4)按 `armBody({ type: '<字面量>'` 抽取铸点 ——
|
|
1108
|
+
* 把字面量藏进参数会让那道门对这两条臂整个失明。
|
|
1109
|
+
*
|
|
1110
|
+
* ── 🔴 载荷是**信封原样**,本层一个键都不摘 ─────────────────────────────────────────────────
|
|
1111
|
+
* sdk 把这两条臂的载荷声明成 `ApprovalFrameEnvelope`(`type` + `schemaVersion: number` + 开集键),
|
|
1112
|
+
* **刻意不是** v1 帧型 —— 理由 sdk events.d.ts:961-968 逐字:SSE 解析腿只做
|
|
1113
|
+
* `JSON.parse(...) as AgentEvent`、**不跑**谓词,臂若窄化到 v1,一条 `schemaVersion: 2` 的合法帧会
|
|
1114
|
+
* 被类型系统当成 v1 端上来,消费端于是去取 `card.risk.requiresRealApproval` —— 而未知版本的卡
|
|
1115
|
+
* 根本不保证有这些键。
|
|
1116
|
+
* ⇒ 本层只做**信封级**成形判(是不是一只对象 / `schemaVersion` 是不是有限数),把**整只帧**交下去;
|
|
1117
|
+
* 窄化(`isApprovalRequestFrameV1` / `isApprovalRevokeFrameV1`)是**宿主**的动作,契约写在
|
|
1118
|
+
* `seam.ts` 的两个臂头注里。在这里挑键重铸 = 在包里立第二份卡形台账,而那正是上游这条设计要防的。
|
|
1119
|
+
*
|
|
1120
|
+
* ── 成形判不过 ⇒ 调用方报 `dropped('malformed')`,**不是** `none` ─────────────────────────────
|
|
1121
|
+
* 与 `engine_notice` 同规:静默的 `none` 会让「协议帧坏了」这件事在用户面上什么痕迹都不留,而这
|
|
1122
|
+
* 两帧带的是**用户可见的决策内容/状态迁移**。`dropped` 经 `reportDroppedFrame` 走宿主 sink + 留痕。
|
|
1123
|
+
*
|
|
1124
|
+
* 🔴 **不在本层做去重/归属过滤**:`approvalId` 去重、按 `taskId` 归属、开流 preamble 对账,三件都
|
|
1125
|
+
* 需要宿主的**卡集状态**(库这一层没有,也不该有)—— 契约逐条写在 `seam.ts` 的臂注里。
|
|
1126
|
+
*/
|
|
1127
|
+
function approvalFrameBody(ev) {
|
|
1128
|
+
// 🔴 判据锚在**真正决定宿主能不能窄化**的那一个量:`schemaVersion`。它不是有限数 ⇒ 这一帧连
|
|
1129
|
+
// 信封都不成立(宿主的两条腿——v1 窄化与通用卡兜底——都需要它来分流),如实报 malformed。
|
|
1130
|
+
const schemaVersion = typeof ev.schemaVersion === 'number' && Number.isFinite(ev.schemaVersion)
|
|
1131
|
+
? ev.schemaVersion
|
|
1132
|
+
: undefined;
|
|
1133
|
+
if (schemaVersion === undefined)
|
|
1134
|
+
return undefined;
|
|
1135
|
+
// 🔴 帧**原样**(含 `type` 回声与全部开集键)。宿主拿它去跑 sdk 的窄化谓词。
|
|
1136
|
+
return { frame: ev, schemaVersion };
|
|
1137
|
+
}
|
|
996
1138
|
/**
|
|
997
1139
|
* `human_input` → 中性内部账本臂(见 `eventToSdkMessage` 顶部的 raw 预分派注释)。
|
|
998
1140
|
*
|
|
@@ -70,10 +70,10 @@
|
|
|
70
70
|
* 是净损失。
|
|
71
71
|
*
|
|
72
72
|
* 成本取数 = `TaskStats.costMicroUsd`(整数 micro-USD 单轨,SDK 1.0.0 / server 1.319.0 /
|
|
73
|
-
* core 2.0.0 起;legacy float `costUsd` 已删,见 costOrNull 头注)
|
|
74
|
-
* (
|
|
75
|
-
* Capabilities.pricingConfigured
|
|
76
|
-
*
|
|
73
|
+
* core 2.0.0 起;legacy float `costUsd` 已删,见 costOrNull 头注)。**零成本不再 ambiguous**
|
|
74
|
+
* (0.65.0 / B-073 ①):显式 `0` = 声明免费、键缺席 = 没定价,两档在 wire 上可分,包边界照实转;
|
|
75
|
+
* 部署级「这台 worker 到底配没配价表」仍可另问 `Capabilities.pricingConfigured`(contract 02
|
|
76
|
+
* §2.10 VERIFY)。本投影器只做单位换算(/1e6)并原样过境。
|
|
77
77
|
*/
|
|
78
78
|
import type { AgentEvent } from '@sema-agent/sdk';
|
|
79
79
|
import { type SDKMessage, type EmitContext } from '../types.js';
|
|
@@ -5,6 +5,15 @@ import { toCcModelUsage } from './turnUsageToModelUsage.js';
|
|
|
5
5
|
// G1 去字面化(2026-08-04):到限/结构化输出/rewind 三族的码字面收编进单一真源,本文件只 import。
|
|
6
6
|
// 开集纪律不变——下面三个集合仍是**识别表**,`subtypeForErrorCode` 的 default 臂才是开集的兑现处。
|
|
7
7
|
import { LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_EXCEEDED, LIMITS_MAX_WALLTIME_EXCEEDED, OUTPUT_INVALID, isRewindFamilyCode, } from '../../engineErrorCodes.js';
|
|
8
|
+
/**
|
|
9
|
+
* wire 数值 → CC 扁平 usage 里的数值:非数 / 非有限一律 0。
|
|
10
|
+
* 与 `turnUsageToModelUsage.finiteOrZero` 同一条规约(那边是 `ModelUsage` 八键的 mint 点,
|
|
11
|
+
* 这边是 `NonNullableUsage` 五键的);两处**刻意不共用一个导出**,因为它们分属两个可移植闭包
|
|
12
|
+
* 的叶,而这一行判据本身没有会漂的内容(有会漂的内容时才值得上收)。
|
|
13
|
+
*/
|
|
14
|
+
function finiteOr0(v) {
|
|
15
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : 0;
|
|
16
|
+
}
|
|
8
17
|
/**
|
|
9
18
|
* Flatten TaskStats → the CC NonNullableUsage placeholder.
|
|
10
19
|
* 🔴 REF-CC-055(xlate-01):`failedToSdkResult` 此前手抄了一份「五键全零」的字面量,那不是
|
|
@@ -12,10 +21,21 @@ import { LIMITS_MAX_COST_EXCEEDED, LIMITS_MAX_TOKENS_EXCEEDED, LIMITS_MAX_TURNS_
|
|
|
12
21
|
*/
|
|
13
22
|
function flattenUsage(stats) {
|
|
14
23
|
return {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
24
|
+
// 🔴 B-073 ③ 族扫(0.65.0):四格一律走 {@link finiteOr0},不再用 `?? 0`。
|
|
25
|
+
// `??` 只挡 `null`/`undefined` —— 而 `TaskStats` 带 `[key: string]: unknown` 开集索引,
|
|
26
|
+
// wire 是 JSON,一个 `"12"` / `NaN` / `Infinity` 会**原样落进** CC 侧型面写着 `number`
|
|
27
|
+
// 的槽位(编译期全绿,消费端在求和时才炸或静默出错)。`Infinity` 尤其糟:它会被当成真
|
|
28
|
+
// 数字摊进总计(与本文件 `microUsdToUsd` 头注同一条规约)。
|
|
29
|
+
inputTokens: finiteOr0(stats?.promptTokens),
|
|
30
|
+
outputTokens: finiteOr0(stats?.outputTokens),
|
|
31
|
+
cacheReadInputTokens: finiteOr0(stats?.cachedTokens),
|
|
32
|
+
// B-073 ③(L-192② 姊妹):此前是**字面量 0**,而 wire 上一直有这一格
|
|
33
|
+
// (`TaskStats.cacheWriteTokens`,sdk `types.d.ts` 真字节)—— 硬编 0 把一笔真实发生的缓存
|
|
34
|
+
// 写入抹成「没发生」,而 CC 的 footer/账单面正是拿这一格算缓存成本的。
|
|
35
|
+
// 缺席仍折 0:CC `NonNullableUsage` 的这一格型面上是必填 number,而它与**成本**那一格不同
|
|
36
|
+
// (token 计数没报就是没写过),缺席与真 0 在 wire 上本来就同义 —— 成本那一格的缺席是 core
|
|
37
|
+
// 刻意造出来的第三档,故单有判别位(见 `toCcModelUsage`)。
|
|
38
|
+
cacheCreationInputTokens: finiteOr0(stats?.cacheWriteTokens),
|
|
19
39
|
webSearchRequests: 0,
|
|
20
40
|
};
|
|
21
41
|
}
|
|
@@ -38,11 +58,13 @@ function microUsdToUsd(micro) {
|
|
|
38
58
|
return typeof micro === 'number' && Number.isFinite(micro) ? micro / 1e6 : null;
|
|
39
59
|
}
|
|
40
60
|
/**
|
|
41
|
-
* P1-5 — `total_cost_usd` truthfulness:
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* (
|
|
61
|
+
* P1-5 — `total_cost_usd` truthfulness: 报**引擎真的说出来的那个数**;引擎**没说**(键缺席 /
|
|
62
|
+
* 非有限)才报 `null`(JSON 消费者看到 "unknown",绝不是一个编出来的零)。
|
|
63
|
+
* 🔴 **0.65.0(B-073 ①)口径订正**:P1-5 当初写的是「只有真正的正成本才报,其余一律 null」,
|
|
64
|
+
* 前提是那时 `0` 分不清「免费」与「没配 MODEL_COST_*」。core 之后把这两件事在 wire 上**分开**
|
|
65
|
+
* (缺席 = 没定价;显式 0 = 声明免费),旧口径于是开始把一条真免费的 run 渲成「不知道」——
|
|
66
|
+
* 那是同一条病的反向。现在的口径见 {@link costOrNull}。CC 的 schema 字段是 numeric ——
|
|
67
|
+
* number-guarded 的读者(replEntry cost fold)本来就把非 number 当缺席。
|
|
46
68
|
*
|
|
47
69
|
* 🔴 键迁移([2006] cli 自查最重命中,2026-07-29):取数源从 legacy float `TaskStats.costUsd` 换成
|
|
48
70
|
* 整数 `TaskStats.costMicroUsd`。`costUsd` 已被 **core 2.0.0 / server 1.319.0 / SDK 1.0.0 同批删除**
|
|
@@ -57,8 +79,16 @@ function microUsdToUsd(micro) {
|
|
|
57
79
|
* 本仓 [clean-cut-no-legacy-compat])。
|
|
58
80
|
*/
|
|
59
81
|
function costOrNull(stats) {
|
|
60
|
-
|
|
61
|
-
|
|
82
|
+
// 🔴 B-073 ①(0.65.0):修前是 `usd !== null && usd > 0 ? usd : null` —— 一条**数值真值判定
|
|
83
|
+
// 当存在性判定**(§B10 病族)的教科书例。core 早已把两件事在 wire 上分开:
|
|
84
|
+
// · `costMicroUsd` **ABSENT** = 有一笔花销没定价 ⇒ 报 `null`(不知道);
|
|
85
|
+
// · `costMicroUsd === 0` = 这台模型**显式声明免费**(core 逐字
|
|
86
|
+
// `an explicit all-zero Model.cost still reports 0`)⇒ 就该报 `0`。
|
|
87
|
+
// 旧写法把「声明免费」也渲成 `null`,于是一条真免费的 run 在 `-p --output-format json` 上
|
|
88
|
+
// 说不出「免费」;而它当初写成 `> 0` 的理由(「0 可能是没配 MODEL_COST_*」)在 core 把缺席
|
|
89
|
+
// 做成可表达的那一刻就失效了 —— 那一档现在由**缺席**自己承载,不必再借 0 当哨兵。
|
|
90
|
+
// 负数照报:退款/修正在引擎账面上是合法值,包不当第二个会计(非有限值仍由 microUsdToUsd 判掉)。
|
|
91
|
+
return microUsdToUsd(stats?.costMicroUsd);
|
|
62
92
|
}
|
|
63
93
|
// 件2a(中断事故修复批 G,2026-07-15,症状2 第一环):参数放宽 TaskStats | undefined + 全链守卫。
|
|
64
94
|
// 409 active-run 拒绝时引擎的 done 帧本体是 {status:'failed', errorMessage, activeTaskId} —— NO stats
|
|
@@ -113,7 +143,8 @@ function modelUsageFor(stats, model) {
|
|
|
113
143
|
inputTokens: stats?.promptTokens,
|
|
114
144
|
outputTokens: stats?.outputTokens,
|
|
115
145
|
cacheReadTokens: stats?.cachedTokens,
|
|
116
|
-
|
|
146
|
+
// B-073 ③ 族扫:合成行是 `flattenUsage` 的**同形第二处**,同批一并改读真值。
|
|
147
|
+
cacheWriteTokens: stats?.cacheWriteTokens,
|
|
117
148
|
costMicroUsd: stats?.costMicroUsd,
|
|
118
149
|
}),
|
|
119
150
|
};
|
|
@@ -50,6 +50,27 @@ export interface EngineUsageLike {
|
|
|
50
50
|
readonly cacheWriteTokens?: number | undefined;
|
|
51
51
|
readonly costMicroUsd?: number | undefined;
|
|
52
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* B-073 ②(0.65.0)—— CC `ModelUsage` 的 sema 超集形:多一个**成本缺席判别位**。
|
|
55
|
+
*
|
|
56
|
+
* 🔴 **为什么这一位必须长在 CC 镜像上,而 `totalInputTokens` 不可以**(与本文件头注引的 [2295]
|
|
57
|
+
* 「CC 形状不承载非 CC 语义」**不矛盾**,差别有出处,写在这里免得下一棒读成漂移):
|
|
58
|
+
* · `totalInputTokens` 有**另一条逐字通道**(`EngineTurnUsage` / runStream 的 `engineUsage` 键),
|
|
59
|
+
* 要总量的消费面去那儿取即可 ⇒ 镜像不必长第二个座位;
|
|
60
|
+
* · **成本缺席没有任何别的载体**:CC 的 `costUSD` 型面上是**必填 number**,「这笔花销没定价」
|
|
61
|
+
* 在 CC 形里**根本不可表达**。不加这一位,唯一的写法就是 `costUSD: 0` —— 那正是 core 逐字
|
|
62
|
+
* 禁止的 `a fabricated 0`(它把「没有价表」和「声明免费」两件事折成同一个字节)。
|
|
63
|
+
* 🔴 **两键合读**,这一位单独没有意义:
|
|
64
|
+
* · `costUSD === 0` 且本位**缺席** ⇒ 引擎显式报了 0 = **声明免费**;
|
|
65
|
+
* · `costUSD === 0` 且本位 `true` ⇒ 引擎**没报** = 没定价,这一行的账**不知道**,别进总计。
|
|
66
|
+
* 只读 `costUSD` 的旧消费者行为**逐字不变**(它仍是那个 0)—— 这是 additive 的全部含义。
|
|
67
|
+
* 🔴 **只在缺席时铸,绝不铸 `false`**(本包 additive 一贯纪律:诚实缺席 = 键不在场;
|
|
68
|
+
* 「键在值假」会让消费端以为这是一个三态位)。
|
|
69
|
+
*/
|
|
70
|
+
export type SemaModelUsage = ModelUsage & {
|
|
71
|
+
/** 在场且为 `true` ⇒ 这一行的 `costUSD: 0` 是「**没定价**」,不是「免费」。 */
|
|
72
|
+
readonly _sema_cost_absent?: true;
|
|
73
|
+
};
|
|
53
74
|
/**
|
|
54
75
|
* 🔴 **CC `ModelUsage` 八键形状的唯一 mint 点**(REF-CC-062 / xlate-08,E1 单源构造)。
|
|
55
76
|
*
|
|
@@ -71,6 +92,6 @@ export interface EngineUsageLike {
|
|
|
71
92
|
* `microUsdToUsd() ?? 0`(非有限即 0)。统一取后者:Infinity 会被下游当成真数字参与求和,把一条
|
|
72
93
|
* 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
|
|
73
94
|
*/
|
|
74
|
-
export declare function toCcModelUsage(raw: EngineUsageLike):
|
|
75
|
-
export declare function turnUsageToModelUsage(usage: TurnUsage):
|
|
95
|
+
export declare function toCcModelUsage(raw: EngineUsageLike): SemaModelUsage;
|
|
96
|
+
export declare function turnUsageToModelUsage(usage: TurnUsage): SemaModelUsage;
|
|
76
97
|
export {};
|
|
@@ -24,15 +24,21 @@ function finiteOrZero(v) {
|
|
|
24
24
|
* 离谱账静默摊进总计,比 0 更糟(与 SDK `taskCostMicroUsd()` 的规约同义)。
|
|
25
25
|
*/
|
|
26
26
|
export function toCcModelUsage(raw) {
|
|
27
|
+
// B-073 ②:成本这一格与其余七格**判据不同** —— 其余格的缺席在 wire 上与真 0 同义(token 计数
|
|
28
|
+
// 没报就是没花),而成本的缺席是 core **刻意**造出来的一档语义(`ABSENT when any spend was
|
|
29
|
+
// unpriced … rather than a fabricated 0`)。故这里单独判一次,而不是继续走 `finiteOrZero`。
|
|
30
|
+
const costPriced = typeof raw.costMicroUsd === 'number' && Number.isFinite(raw.costMicroUsd);
|
|
27
31
|
return {
|
|
28
32
|
inputTokens: finiteOrZero(raw.inputTokens),
|
|
29
33
|
outputTokens: finiteOrZero(raw.outputTokens),
|
|
30
34
|
cacheReadInputTokens: finiteOrZero(raw.cacheReadTokens),
|
|
31
35
|
cacheCreationInputTokens: finiteOrZero(raw.cacheWriteTokens),
|
|
32
36
|
webSearchRequests: 0, // dropped on the wire
|
|
33
|
-
|
|
37
|
+
// CC 形不破:值仍是 number。「不知道」由同行的判别位说,见 {@link SemaModelUsage}。
|
|
38
|
+
costUSD: costPriced ? raw.costMicroUsd / 1_000_000 : 0,
|
|
34
39
|
contextWindow: 0, // dropped on the wire → mock-fill (static per-model table)
|
|
35
40
|
maxOutputTokens: 0, // dropped on the wire → mock-fill
|
|
41
|
+
...(costPriced ? {} : { _sema_cost_absent: true }),
|
|
36
42
|
};
|
|
37
43
|
}
|
|
38
44
|
export function turnUsageToModelUsage(usage) {
|
|
@@ -351,6 +351,13 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
351
351
|
// the spinner-token feeder, verify/TOKEN-187-PORT.md step 3). `turn_end` carries no renderable content, so
|
|
352
352
|
// this arm is metrics-only; the bridge converts it to a `message_delta{usage.output_tokens}` StreamEvent.
|
|
353
353
|
if (ev.type === 'turn_end') {
|
|
354
|
+
// 🔴 L-215③ 异源对抗复审 [medium]②:`usageMissing` 是 core **刻意**造出来的诚实缺席位
|
|
355
|
+
// (臂注逐字:「Consumers must treat the missing usage as UNKNOWN — not zero」;它与
|
|
356
|
+
// `usage` **可以同帧**)。把它剥掉,本批新开的这条 usage 通道就会把「不知道」渲成一笔
|
|
357
|
+
// 全零的已知账 —— 与本批要根治的病(B-073 的成本 0/缺席)逐字同形,只是换了个量。
|
|
358
|
+
const usageMissing = ev.usageMissing === true;
|
|
359
|
+
const stopReasonRaw = ev.stopReason;
|
|
360
|
+
const stopWord = typeof stopReasonRaw === 'string' && stopReasonRaw.length > 0 ? stopReasonRaw : undefined;
|
|
354
361
|
const usage = turnEndUsage(ev);
|
|
355
362
|
// §E2 identity (service 1.78) — a SUB-FLOW's turn_end (orchestration/subagent round, carries
|
|
356
363
|
// parentToolCallId) must NOT drive the leader's C1a `end` reconcile: its outputTokens are the
|
|
@@ -379,14 +386,44 @@ async function* runStreamInner(events, ctx, handle = {}) {
|
|
|
379
386
|
laneProof: MAIN,
|
|
380
387
|
usage,
|
|
381
388
|
...(ev.usage !== undefined ? { engineUsage: ev.usage } : {}),
|
|
389
|
+
// L-215③:chrome 腿同批带这两位(message 腿的对偶在 `turn_usage` 臂的 `_sema_` 键上)。
|
|
390
|
+
// 🔴 `usageMissing` 在这条腿上**不能**靠「不发 usage」表达 —— 本臂的 `usage` 是必填位
|
|
391
|
+
// (宿主义务是「落最近一次 turn 真 usage」),所以它只能以判别位在场:
|
|
392
|
+
// `usageMissing === true` ⇒ 同行那份 usage **不是**一笔已知的账,别当真值落槽。
|
|
393
|
+
...(usageMissing ? { usageMissing: true } : {}),
|
|
394
|
+
...(stopWord !== undefined ? { stopReason: stopWord } : {}),
|
|
382
395
|
});
|
|
383
396
|
}
|
|
384
397
|
catch { /* fail-soft — statusline 退回 null,原语义 */ }
|
|
385
398
|
}
|
|
386
399
|
}
|
|
387
400
|
const outputTokens = ev.usage?.outputTokens;
|
|
388
|
-
|
|
389
|
-
|
|
401
|
+
// 🔴 异源对抗复审 [medium]③:发臂条件从「有 outputTokens」放宽到「**有话可说**」——
|
|
402
|
+
// core 会发 `{type:'turn_end', usageMissing:true, stopReason:'error'}` 这种合法帧,而
|
|
403
|
+
// 旧条件让它整条静默 ⇒ 「这一轮为什么停」这条机读位在最需要它的那一刻(出错/中止)不见了。
|
|
404
|
+
// ⚠️ 旧消费者零影响:`outputTokens` 读不出时**整键不铸**,而 adapt 的 `turnUsageArm`
|
|
405
|
+
// 本来就以 `typeof m.outputTokens === 'number'` 开门 ⇒ 这种帧对它是 no-op。
|
|
406
|
+
if ((typeof outputTokens === 'number' || stopWord !== undefined) && !isSubFlow) {
|
|
407
|
+
// ── L-215③(0.65.0):assistant 行那两个**算不出来**的键的真值出口 ─────────────────
|
|
408
|
+
// `eventToSdkMessage` 的 `assistantArm` 刻意**不**在内容臂上铸 `usage` / `stop_reason`
|
|
409
|
+
// (帧序:内容臂先到、`turn_end` 后到 ⇒ 臂发出时引擎还没说这一轮花了多少;在那里铸只能
|
|
410
|
+
// 是估算,而估算正是本件要根治的病)。真值只能在**这里**给 —— 这条臂本来就是 turn 收尾
|
|
411
|
+
// 那一拍的中性出口。两个都是 `_sema_` 超集键,CC 同名键语义零改:
|
|
412
|
+
// · `_sema_last_assistant_usage` —— 这一轮的 CC `ModelUsage` 镜像(与 footer 折叠用的
|
|
413
|
+
// 是**同一只** `turnEndUsage()` 产物,不另铸第二份 ⇒ 两面永远不会各漂各的);
|
|
414
|
+
// · `_sema_stop_reason` —— `turn_end.stopReason` **原词透传**(core 归一化后的五词
|
|
415
|
+
// `stop`/`length`/`toolUse`/`error`/`aborted`,sdk 型面是开放 string ⇒ 按开集读;
|
|
416
|
+
// 「这一轮是不是被 max_tokens 截了」就靠它,此前 stream 与 trace 两面互盲)。
|
|
417
|
+
// 缺席一律不铸(旧引擎不发 `stopReason`;`usage` 整体缺席时本臂根本不发,见上面的 if)。
|
|
418
|
+
yield {
|
|
419
|
+
type: 'turn_usage',
|
|
420
|
+
...(typeof outputTokens === 'number' ? { outputTokens } : {}),
|
|
421
|
+
// 🔴 `usageMissing` 在场 ⇒ **不铸镜像**(铸了就是把「不知道」写成一笔全零的已知账),
|
|
422
|
+
// 改铸判别位。两键互斥,消费方一看就知道这一轮的账是不是可信。
|
|
423
|
+
...(usage !== undefined && !usageMissing ? { _sema_last_assistant_usage: usage } : {}),
|
|
424
|
+
...(usageMissing ? { _sema_usage_missing: true } : {}),
|
|
425
|
+
...(stopWord !== undefined ? { _sema_stop_reason: stopWord } : {}),
|
|
426
|
+
};
|
|
390
427
|
}
|
|
391
428
|
continue;
|
|
392
429
|
}
|