@sema-agent/server 3.24.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bench/s1/run-firm.js +1 -1
- package/dist/budget.d.ts +6 -0
- package/dist/budget.js +18 -2
- package/dist/config.js +4 -2
- package/dist/fleet/fleet-bus.js +10 -7
- package/dist/fleet-client.d.ts +4 -0
- package/dist/fleet-client.js +4 -0
- package/dist/observability/metrics.js +7 -2
- package/dist/plugins/remote-env-host.js +2 -1
- package/dist/plugins/worktree-isolation.js +6 -1
- package/dist/trace/project.d.ts +21 -2
- package/dist/trace/project.js +38 -6
- package/dist/usage-analytics.d.ts +9 -3
- package/dist/usage-analytics.js +4 -0
- package/package.json +2 -2
|
@@ -285,7 +285,7 @@ function dryRunMockFactory() {
|
|
|
285
285
|
runWithVerification: async () => vr,
|
|
286
286
|
runWithVerificationSup: async () => vr,
|
|
287
287
|
resumeWithVerification: async () => vr,
|
|
288
|
-
runRepairLoop: async () => ({ ...vr, terminal: "candidate_only", bundle: { failureTrace: "", diagnostics: [], rejectedHypotheses: [], attemptCount: 1, oracleTier: "trusted_hidden" } }),
|
|
288
|
+
runRepairLoop: async () => ({ ...vr, terminal: "candidate_only", oracleCostMicroUsd: 0, bundle: { failureTrace: "", diagnostics: [], rejectedHypotheses: [], attemptCount: 1, oracleTier: "trusted_hidden" } }),
|
|
289
289
|
getCheckpoint: async () => null,
|
|
290
290
|
runLeaderTask: async () => ({ ok: true, reports: [], merge: undefined, workerBudgetsUsd: [0.5, 0.5] }),
|
|
291
291
|
runOracle: async () => oracle,
|
package/dist/budget.d.ts
CHANGED
|
@@ -46,6 +46,12 @@ export interface ModelUsageDelta {
|
|
|
46
46
|
* = 运营者声明免费 ⇒ 键在场且为 0。两者可区分,这正是 #59 折零案的源头修复。
|
|
47
47
|
* 与 SDK `ModelUsageDelta`(五键全可选)/ `usage-analytics.UsageModelEntry` 同轨。 */
|
|
48
48
|
costMicroUsd?: number;
|
|
49
|
+
/** core 3.0.0([2296] 装车单 S10,§DESIGN-V2 V2-⑤)可选口径标记:标出这一行的 token 分量是在哪套记账
|
|
50
|
+
* 基准下写的(当前唯一值 = `"uncached-components-v1"`:`inputTokens` = cache-MISS 分量,`cacheRead`/
|
|
51
|
+
* `cacheWrite` 分列)。**写点在 `trace/project.ts` 的 durable 行构造处**(`appendModelUsageDelta`),
|
|
52
|
+
* 不在这里 —— {@link ModelUsageTracker.record} 的累加逻辑本身零改动,永远不设置这个键(它只在 drain
|
|
53
|
+
* 之后、落盘之前被打上)。缺席 = 落盘更早、口径未知的存量行(usage-analytics 读端据此诚实标注)。 */
|
|
54
|
+
usageBasis?: string;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
57
|
* E8 (shell-host contract): per-task × per-model usage accumulator, fed (synchronously) by the `brain.call`
|
package/dist/budget.js
CHANGED
|
@@ -206,16 +206,29 @@ export function createTracer(metrics, costQuota, modelUsage, fleetUsage, fleetLe
|
|
|
206
206
|
// they neither leak nor enter the echo; their spend is in TaskStats.nested). Drained at turn boundaries into the
|
|
207
207
|
// durable log (resume-safe); the registered entry is dropped at run end (clear). NOT cardinality-guarded by
|
|
208
208
|
// model: bounded by a run's model set, and by the active-run set.
|
|
209
|
+
// core 3.0.0([2296] S1):this lane's `inputTokens` stays `e.promptTokens` — DELIBERATELY zero-changed.
|
|
210
|
+
// The E8 ledger's semantics are COMPONENT-wise (MISS + cacheRead/cacheWrite listed separately;
|
|
211
|
+
// usage-analytics' read side SUMS them back into a total), unlike the weightedTokens/fleetUsage lane just
|
|
212
|
+
// below (which wants the pre-summed normalized total and migrates to `e.totalInputTokens`). Pre-3.0.0 the
|
|
213
|
+
// gateway family's `promptTokens` already included cache, so this same read-side sum double-counted for
|
|
214
|
+
// that family — an existing accounting gap in historical rows that this version does not retroactively fix
|
|
215
|
+
// (declared, not silently carried forward): see CHANGELOG's "存量口径断层" note.
|
|
209
216
|
modelUsage?.record(e.taskId, e.model, { inputTokens: e.promptTokens, outputTokens: e.completionTokens, cacheReadTokens: e.cacheRead, cacheWriteTokens: e.cacheWrite, ...(e.costMicroUsd !== undefined ? { costMicroUsd: e.costMicroUsd } : {}) }); // RB-368 透传缺席
|
|
210
217
|
// weight-at-burn:加权 tokens = (prompt+completion)×模型配额倍率,在记账点固化(此后改倍率
|
|
211
218
|
// 不回算历史,registry ModelEntry.quotaWeight 同句契约)。weight 源=config.modelQuotaWeights(registry
|
|
212
219
|
// hot-apply;env lane 空表 ⇒ 1 兜底);非法回调值防御性归 1(tracer 契约 never-throws,不让坏表毒记账)。
|
|
220
|
+
// core 3.0.0([2296] S1):`e.promptTokens` → `e.totalInputTokens`. This axis's contract (line ~365 below)
|
|
221
|
+
// has always been "normalized total input INCLUDING cache" — pre-3.0.0 that's what `promptTokens` meant for
|
|
222
|
+
// the gateway family; 3.0.0 flips `promptTokens` to mean cache-MISS-only, so staying on it here would
|
|
223
|
+
// massively under-count the quota/lease axis for any cache-heavy load (a lease could be walked through by
|
|
224
|
+
// cache-heavy traffic — the exact same failure class as the side-query R4 finding below).
|
|
213
225
|
const qwRaw = quotaWeightFor?.(e.model);
|
|
214
226
|
const qw = typeof qwRaw === "number" && Number.isFinite(qwRaw) && qwRaw > 0 ? qwRaw : 1;
|
|
215
|
-
const weightedTokens = Math.round((e.
|
|
227
|
+
const weightedTokens = Math.round((e.totalInputTokens + e.completionTokens) * qw);
|
|
216
228
|
// P2 用量批报:principal×model 窗口累计(同 ⑤b 的 ALS 归因点 — 子任务花费也归到发起 principal),
|
|
217
229
|
// fleet-client 每窗口 drain→POST /api/fleet/usage。未配 fleet(accumulator 缺席)= 零行为。
|
|
218
|
-
|
|
230
|
+
// core 3.0.0:同上迁移理由,inputTokens=归一化总输入含 cache(totalInputTokens),与 weightedTokens 同轴。
|
|
231
|
+
fleetUsage?.record(currentPrincipal(), e.model, e.taskId, { inputTokens: e.totalInputTokens, outputTokens: e.completionTokens, ...(e.costMicroUsd !== undefined ? { costMicroUsd: e.costMicroUsd } : {}), weightedTokens, quotaWeightAtUse: qw }); // RB-368 透传缺席
|
|
219
232
|
// lease 消费(D4 AP):同一 ALS 归因点做 lease 本地扣减 —— 只对当前持 lease 的 principal
|
|
220
233
|
// 生效(FleetLeaseManager 内部判),sync + never throws。双轴=weightedTokens(配额轴)+costMicroUsd($ 轴)。
|
|
221
234
|
fleetLease?.recordSpend(currentPrincipal(), { weightedTokens, ...(e.costMicroUsd !== undefined ? { costMicroUsd: e.costMicroUsd } : {}) }); // RB-368 透传缺席
|
|
@@ -332,6 +345,9 @@ fleetUsage, fleetLease, quotaWeightFor) {
|
|
|
332
345
|
// (input-excludes-cached)的 cache 命中几乎全部在 input 之外,不加 cacheRead/cacheWrite 就是配额轴
|
|
333
346
|
// 大幅少收(lease 可被 cache-heavy side query 绕穿)。family 未知(catalog 查不到)走**保守臂**
|
|
334
347
|
// (含 cache——OpenAI 族此臂多收,方向与 cost 残差一致:宁多勿少,记档同一条)。
|
|
348
|
+
// core 3.0.0 核实(S1 提货站):此段的输入源是 `SideQueryResult.usage`(llm 层生料 `Usage`,
|
|
349
|
+
// engine/llm/types.d.ts)——不是 3.0.0 翻新过的 `brain.call`/`turn_end` trace 事件面,3.0.0 未动这个类型,
|
|
350
|
+
// 本折算继续承重(零改动)。
|
|
335
351
|
const promptTokens = r.family === "input-includes-cached" ? input : input + cacheRead + cacheWrite;
|
|
336
352
|
// RB-368/#59 同判据(createTracer 上方注释同款纪律):`usage.cost.total` 整键缺席 = 未知(unpriced 部署的
|
|
337
353
|
// side query 真形),不是「花了 $0」——折 0 就是把「不知道」编造成「免费」。已知(哪怕显式 0)才带键。
|
package/dist/config.js
CHANGED
|
@@ -514,8 +514,10 @@ function parseModelDomain() {
|
|
|
514
514
|
}
|
|
515
515
|
// Model.api must reflect the brain that actually serves it (routing is by provider): an "anthropic"
|
|
516
516
|
// provider runs the Anthropic brain, whose usage reports `input` EXCLUDING cached tokens. core ≥1.22
|
|
517
|
-
// normalizes the cache-hit-rate denominator per api family
|
|
518
|
-
//
|
|
517
|
+
// normalizes the cache-hit-rate denominator per api family (`cacheFamilyOf` picks the fold formula from
|
|
518
|
+
// `Model.api`) — a wrong api picks the wrong family, so the normalized total (core 3.0.0: `totalInputTokens`,
|
|
519
|
+
// core's own cacheHitRate = cachedTokens/totalInputTokens) comes out too small and cacheHitRate exceeds
|
|
520
|
+
// 100%. (Brains select by provider, not api, so this only fixes accounting.)
|
|
519
521
|
//
|
|
520
522
|
// Default inference ([792]④, core ruling 2026-07-14): an EXPLICIT MODEL_PROVIDER always wins (unchanged).
|
|
521
523
|
// When it is unset but the operator explicitly configured an Anthropic-protocol base URL AND a matching
|
package/dist/fleet/fleet-bus.js
CHANGED
|
@@ -222,13 +222,16 @@ export function fleetRunPublisher(bus, run) {
|
|
|
222
222
|
return 0;
|
|
223
223
|
if (typeof u.totalTokens === "number")
|
|
224
224
|
return u.totalTokens;
|
|
225
|
-
//
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
|
|
225
|
+
// core 3.0.0([2296] 装车单 S2, [2298] R#9 二次复审并入):`turn_end.usage.inputTokens` 的语义在 3.0.0
|
|
226
|
+
// **翻转**——此前(core ≤2.x)它是 NORMALIZED TOTAL INPUT *INCLUDING CACHE*(usage-accounting
|
|
227
|
+
// promptTokensOf:Anthropic 族 = input+cacheRead+cacheWrite,OpenAI 族已把 cache 折进 input),本函数当时
|
|
228
|
+
// 直接 `inputTokens + outputTokens` 就是整轮总量;3.0.0 起 `inputTokens` 恒为 cache-MISS 分量,新必填的
|
|
229
|
+
// `totalInputTokens` 才是那个「含 cache 的归一化总量」(旧 inputTokens 语义的正统继承者)。继续只加
|
|
230
|
+
// `inputTokens` 会在有 cache 命中时**漏计**(leader 行的展示 token 数偏低)——故总量优先取
|
|
231
|
+
// `totalInputTokens`,旧核帧(尚未升级到 3.0.0、该键缺席)回落 `inputTokens`(防御性:本引擎恒 ≥3.0.0
|
|
232
|
+
// 时两键并存;异常帧宁少算不 NaN)。Display-only(leader fleet 行的 token 数);billing(costMicroUsd)+
|
|
233
|
+
// context/compaction 走 totalTokens,不受影响;child 行走 task_progress.usage.totalTokens(上面的早退臂)。
|
|
234
|
+
return (u.totalInputTokens ?? u.inputTokens ?? 0) + (u.outputTokens ?? 0);
|
|
232
235
|
};
|
|
233
236
|
return {
|
|
234
237
|
onStart() {
|
package/dist/fleet-client.d.ts
CHANGED
|
@@ -31,6 +31,10 @@ export declare class FleetUsageAccumulator {
|
|
|
31
31
|
* 同款=**传染**(同为单 producer 累加器:已知+未知=未知);center 聚合面的「下界 + costUnknown
|
|
32
32
|
* 标记」语义在 registry-core aggregateUsage(跨 worker 审计总账层,两层判据见 [2103])。
|
|
33
33
|
* 配额轴(weightedTokens)不受影响,仍逐笔精确。
|
|
34
|
+
*
|
|
35
|
+
* core 3.0.0([2296] 装车单 S4c)语义注:`d.inputTokens` 在唯一调用点(budget.ts 的
|
|
36
|
+
* `fleetUsage.record(...)`,S1 已迁)传的是 `e.totalInputTokens`(归一化总输入含 cache),不是
|
|
37
|
+
* `e.promptTokens`(3.0.0 起恒 cache-MISS)——上游已切换口径,本函数只是照收不重算,值域/签名零改动。
|
|
34
38
|
*/
|
|
35
39
|
record(principal: string | undefined, model: string, taskId: string, d: {
|
|
36
40
|
inputTokens: number;
|
package/dist/fleet-client.js
CHANGED
|
@@ -31,6 +31,10 @@ export class FleetUsageAccumulator {
|
|
|
31
31
|
* 同款=**传染**(同为单 producer 累加器:已知+未知=未知);center 聚合面的「下界 + costUnknown
|
|
32
32
|
* 标记」语义在 registry-core aggregateUsage(跨 worker 审计总账层,两层判据见 [2103])。
|
|
33
33
|
* 配额轴(weightedTokens)不受影响,仍逐笔精确。
|
|
34
|
+
*
|
|
35
|
+
* core 3.0.0([2296] 装车单 S4c)语义注:`d.inputTokens` 在唯一调用点(budget.ts 的
|
|
36
|
+
* `fleetUsage.record(...)`,S1 已迁)传的是 `e.totalInputTokens`(归一化总输入含 cache),不是
|
|
37
|
+
* `e.promptTokens`(3.0.0 起恒 cache-MISS)——上游已切换口径,本函数只是照收不重算,值域/签名零改动。
|
|
34
38
|
*/
|
|
35
39
|
record(principal, model, taskId, d) {
|
|
36
40
|
const p = principal ?? "__anonymous__"; // single-user turnkey: no principal — still meter, under a stable bucket
|
|
@@ -213,8 +213,13 @@ export function createMetrics() {
|
|
|
213
213
|
m.counter("council_runs_total", "Code-review council runs (L1+L3)");
|
|
214
214
|
m.histogram("council_tokens", "Total model tokens per council review (lenses + arbiter)");
|
|
215
215
|
// Prefix-cache effectiveness (core 1.22): per-task hit rate (cost-critical) + a count of tasks the
|
|
216
|
-
// Runner flagged as low-hit (unstable/poisoned prefix) so it can be alerted on.
|
|
217
|
-
|
|
216
|
+
// Runner flagged as low-hit (unstable/poisoned prefix) so it can be alerted on. The observed value is
|
|
217
|
+
// `result.stats.cacheHitRate` — core-computed (core 3.0.0: cachedTokens/totalInputTokens, the normalized
|
|
218
|
+
// total-including-cache denominator; runs.ts / http/server.ts just forward it), not derived here — this
|
|
219
|
+
// description names the field, not a formula this file owns (E3: don't restate a formula whose owner
|
|
220
|
+
// can change it without telling us; core 3.0.0's denominator switched from `promptTokens` to
|
|
221
|
+
// `totalInputTokens`, which the old "(cachedTokens/promptTokens)" text would have silently mis-described).
|
|
222
|
+
m.histogram("task_cache_hit_rate", "Prefix-cache hit rate per task (core-computed stats.cacheHitRate, 0..1)", [
|
|
218
223
|
0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95, 1,
|
|
219
224
|
]);
|
|
220
225
|
m.counter("prompt_cache_low_hit_total", "Tasks the Runner flagged with a low prefix-cache hit rate");
|
|
@@ -867,7 +867,8 @@ export class RemoteHostExecutionEnv {
|
|
|
867
867
|
}
|
|
868
868
|
catch (e) {
|
|
869
869
|
// 同步 spawn throw(如 cwd=文件的 ENOTDIR)——与管道旧形字节级对齐:executor 内 re-throw=Promise
|
|
870
|
-
// reject(
|
|
870
|
+
// reject(消费方按 await 抛感知传输层失败;吞成 Result err 会让上层静默。⚠️ 引例更新:core 3.0.0
|
|
871
|
+
// 起 pruneWorktrees 改回执形 {ok},其消费点已双臂消费——worktree-isolation.ts;本处 spawn 层契约不变)。
|
|
871
872
|
closeSpoolFds();
|
|
872
873
|
cleanupSpool();
|
|
873
874
|
throw e;
|
|
@@ -227,7 +227,12 @@ export async function reapOrphanWorktrees(baseEnvForGit, repoRoot, logger) {
|
|
|
227
227
|
try {
|
|
228
228
|
// #62:prune 动的是与 add/remove 同一张 `.git/worktrees` 注册表 ⇒ 必须过同一把 per-repoRoot 锁
|
|
229
229
|
// (曾绕锁直跑;恶劣臂见 serializeGitAdmin 注)。
|
|
230
|
-
|
|
230
|
+
// core 3.0.0(#94 提货):pruneWorktrees 从「失败 throw」改为回执 `{ok:true}|{ok:false,detail}`——
|
|
231
|
+
// 只靠下面的 catch 发警在 3.0.0 下= prune 失败**静默化**(CI svc3 钉抓出)。回执臂与 throw 臂
|
|
232
|
+
// (serializeGitAdmin/env 传输层仍可抛)两路都进同一条 warn,判据不变:失败必须留痕。
|
|
233
|
+
const r = await serializeGitAdmin(repoRoot, () => pruneWorktrees(baseEnvForGit, norm(repoRoot)));
|
|
234
|
+
if (r.ok === false)
|
|
235
|
+
logger?.warn?.("worktree_prune_failed", { repoRoot: norm(repoRoot), error: r.detail });
|
|
231
236
|
}
|
|
232
237
|
catch (e) {
|
|
233
238
|
logger?.warn?.("worktree_prune_failed", { repoRoot: norm(repoRoot), error: e instanceof Error ? e.message : String(e) });
|
package/dist/trace/project.d.ts
CHANGED
|
@@ -19,7 +19,13 @@ type AppendFn = (type: string, data: unknown) => Promise<void>;
|
|
|
19
19
|
type GetEventsFn = (taskId: string, afterSeq: number) => Promise<RunEvent[]>;
|
|
20
20
|
/** E8 (shell-host): drain the per-task model-usage accumulator into an append-only `model_usage` DELTA event.
|
|
21
21
|
* Shared by BOTH durable-write legs (runInBackground + the resume leg) so the persisted shape never drifts; the
|
|
22
|
-
* append-only deltas make a suspended/resumed run's total resume-safe (each leg appends its own, the echo sums).
|
|
22
|
+
* append-only deltas make a suspended/resumed run's total resume-safe (each leg appends its own, the echo sums).
|
|
23
|
+
*
|
|
24
|
+
* core 3.0.0([2296] 装车单 S10,§DESIGN-V2 V2-⑤): every drained row is tagged with `usageBasis:
|
|
25
|
+
* "uncached-components-v1"` — the write point for this label (the tracker itself, {@link ModelUsageTracker},
|
|
26
|
+
* never sets it; it stays a pure token/cost accumulator). This is what lets {@link aggregateModelUsage} tell
|
|
27
|
+
* apart rows written under the current component semantics from older rows (pre-dating this label) when it
|
|
28
|
+
* sums across a run's whole event log. */
|
|
23
29
|
export declare function appendModelUsageDelta(append: AppendFn, modelUsage: ModelUsageTracker | undefined, taskId: string): Promise<void>;
|
|
24
30
|
/** [998]② drain the pending prompt-manifest records (whitelisted at RECORD time, budget.ts — ids/digests/
|
|
25
31
|
* counts only, no prompt bodies by core's manifest contract) into durable `prompt_assembled` events, one per
|
|
@@ -287,9 +293,15 @@ export interface TraceTurn {
|
|
|
287
293
|
ts: string;
|
|
288
294
|
role: "system" | "user" | "assistant" | "tool";
|
|
289
295
|
blocks: TraceBlock[];
|
|
296
|
+
/** core 3.0.0([2296] 装车单 S3,§DESIGN-V2 V2-②):这是**本服务自有的投影契约**,不是 core wire 帧的
|
|
297
|
+
* verbatim 转发——`in` 刻意保持它历史上的「总量」语义不变(旧帧/新帧读端零伤),不随 core 的
|
|
298
|
+
* `promptTokens`/`turn_end.usage.inputTokens` 翻转为 cache-MISS。新增 `uncachedIn` 承接 core 3.0.0 才有的
|
|
299
|
+
* cache-MISS 分量(命名取自 core `uncachedInputTokensOf`),只在能确认自己踩在新契约帧上时才发
|
|
300
|
+
* (见 {@link toContractTokens})。 */
|
|
290
301
|
tokens?: {
|
|
291
302
|
in: number;
|
|
292
303
|
out: number;
|
|
304
|
+
uncachedIn?: number;
|
|
293
305
|
cacheRead?: number;
|
|
294
306
|
cacheWrite?: number;
|
|
295
307
|
};
|
|
@@ -326,7 +338,14 @@ export declare function turnEndEventData(ev: {
|
|
|
326
338
|
stopReason?: string;
|
|
327
339
|
}): Record<string, unknown>;
|
|
328
340
|
/** Map core's `turn_end.usage` (1.76 / core [R59]: inputTokens/outputTokens/cacheReadTokens/cacheWriteTokens/
|
|
329
|
-
* costMicroUsd) → the contract's `TraceTurn.tokens
|
|
341
|
+
* costMicroUsd; core 3.0.0 adds required `totalInputTokens`) → the contract's `TraceTurn.tokens`.
|
|
342
|
+
* Absent/empty → undefined.
|
|
343
|
+
*
|
|
344
|
+
* core 3.0.0([2296] 装车单 S3,§DESIGN-V2 V2-②,codex 复审「异议」采纳):`TraceTurn.tokens` 是本服务自有
|
|
345
|
+
* 的投影契约,不是 wire verbatim——`in` 保持它一直以来的**总量**语义,3.0.0 起改取 `totalInputTokens`
|
|
346
|
+
* (归一化总输入含 cache)优先,`inputTokens` 回落(旧帧/尚未升级的 core 下 `totalInputTokens` 缺席时,
|
|
347
|
+
* `in` 落回旧值,读端零伤)。`uncachedIn` 只在**能确认自己踩在新契约帧上**(`totalInputTokens` 在场)时
|
|
348
|
+
* 才发——旧帧的 `inputTokens` 是族依赖的旧语义,标成「uncachedIn(cache-MISS)」会是谎言。 */
|
|
330
349
|
export declare function toContractTokens(usage: unknown): TraceTurn["tokens"] | undefined;
|
|
331
350
|
export interface ProjectOpts {
|
|
332
351
|
/** Prepend the user turn (the objective is not in the event log; the endpoint supplies it). */
|
package/dist/trace/project.js
CHANGED
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { redactSecrets, redactDeep } from "./redact.js";
|
|
2
2
|
/** E8 (shell-host): drain the per-task model-usage accumulator into an append-only `model_usage` DELTA event.
|
|
3
3
|
* Shared by BOTH durable-write legs (runInBackground + the resume leg) so the persisted shape never drifts; the
|
|
4
|
-
* append-only deltas make a suspended/resumed run's total resume-safe (each leg appends its own, the echo sums).
|
|
4
|
+
* append-only deltas make a suspended/resumed run's total resume-safe (each leg appends its own, the echo sums).
|
|
5
|
+
*
|
|
6
|
+
* core 3.0.0([2296] 装车单 S10,§DESIGN-V2 V2-⑤): every drained row is tagged with `usageBasis:
|
|
7
|
+
* "uncached-components-v1"` — the write point for this label (the tracker itself, {@link ModelUsageTracker},
|
|
8
|
+
* never sets it; it stays a pure token/cost accumulator). This is what lets {@link aggregateModelUsage} tell
|
|
9
|
+
* apart rows written under the current component semantics from older rows (pre-dating this label) when it
|
|
10
|
+
* sums across a run's whole event log. */
|
|
5
11
|
export async function appendModelUsageDelta(append, modelUsage, taskId) {
|
|
6
12
|
const delta = modelUsage?.drain(taskId);
|
|
7
|
-
if (delta)
|
|
8
|
-
|
|
13
|
+
if (delta) {
|
|
14
|
+
const tagged = {};
|
|
15
|
+
for (const [model, d] of Object.entries(delta))
|
|
16
|
+
tagged[model] = { ...d, usageBasis: "uncached-components-v1" };
|
|
17
|
+
await append("model_usage", { usage: tagged });
|
|
18
|
+
}
|
|
9
19
|
}
|
|
10
20
|
/** [998]② drain the pending prompt-manifest records (whitelisted at RECORD time, budget.ts — ids/digests/
|
|
11
21
|
* counts only, no prompt bodies by core's manifest contract) into durable `prompt_assembled` events, one per
|
|
@@ -61,6 +71,11 @@ export function aggregateModelUsage(events) {
|
|
|
61
71
|
const num = (v) => (typeof v === "number" && Number.isFinite(v) ? v : 0);
|
|
62
72
|
const total = {};
|
|
63
73
|
const costUnknown = new Set(); // RB-368:该模型至少有一行没带 costMicroUsd 键 ⇒ 总量不可知
|
|
74
|
+
// S10([2296] 装车单,§DESIGN-V2 V2-⑤):usageBasis 只在**这个模型贡献的每一行**都带同一个值时才透传到
|
|
75
|
+
// 回声行——混窗(升级前落盘的旧行 / 坏行 / 未来换了别的 basis 字符串)一律诚实省略,不猜、不传染出一个
|
|
76
|
+
// 假的一致标记。Map 记「目前为止这个模型见过的一致值」:第一行定值;之后任何一行不同(含缺席,记作
|
|
77
|
+
// undefined)⇒ 永久锁定为 undefined(一旦分歧就回不去)。
|
|
78
|
+
const basisSeen = new Map();
|
|
64
79
|
let any = false;
|
|
65
80
|
for (const ev of events) {
|
|
66
81
|
if (ev.type !== "model_usage")
|
|
@@ -74,6 +89,7 @@ export function aggregateModelUsage(events) {
|
|
|
74
89
|
// 之后,对唯一能判别的形(null)先抛,守卫不可达;坏行按整行 0 折(#59 同口径),cost 记未知。
|
|
75
90
|
if (d === null || typeof d !== "object") {
|
|
76
91
|
costUnknown.add(model);
|
|
92
|
+
basisSeen.set(model, undefined); // 坏行 = 不可知,视同分歧,不透传 basis
|
|
77
93
|
continue;
|
|
78
94
|
}
|
|
79
95
|
const cur = total[model] ?? { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0, costMicroUsd: 0 };
|
|
@@ -87,11 +103,19 @@ export function aggregateModelUsage(events) {
|
|
|
87
103
|
cur.costMicroUsd = num(cur.costMicroUsd) + num(d.costMicroUsd);
|
|
88
104
|
else
|
|
89
105
|
costUnknown.add(model);
|
|
106
|
+
const rowBasis = typeof d.usageBasis === "string" ? d.usageBasis : undefined;
|
|
107
|
+
if (!basisSeen.has(model))
|
|
108
|
+
basisSeen.set(model, rowBasis);
|
|
109
|
+
else if (basisSeen.get(model) !== rowBasis)
|
|
110
|
+
basisSeen.set(model, undefined);
|
|
90
111
|
total[model] = cur;
|
|
91
112
|
}
|
|
92
113
|
}
|
|
93
114
|
for (const model of costUnknown)
|
|
94
115
|
delete total[model]?.costMicroUsd;
|
|
116
|
+
for (const [model, basis] of basisSeen)
|
|
117
|
+
if (basis !== undefined && total[model])
|
|
118
|
+
total[model].usageBasis = basis;
|
|
95
119
|
return any ? total : undefined;
|
|
96
120
|
}
|
|
97
121
|
/** Identity fields core stamps on each content event (1.121.0 `TaskEventIdentity`, shell-host contract E2):
|
|
@@ -396,16 +420,24 @@ export function turnEndEventData(ev) {
|
|
|
396
420
|
};
|
|
397
421
|
}
|
|
398
422
|
/** Map core's `turn_end.usage` (1.76 / core [R59]: inputTokens/outputTokens/cacheReadTokens/cacheWriteTokens/
|
|
399
|
-
* costMicroUsd) → the contract's `TraceTurn.tokens
|
|
423
|
+
* costMicroUsd; core 3.0.0 adds required `totalInputTokens`) → the contract's `TraceTurn.tokens`.
|
|
424
|
+
* Absent/empty → undefined.
|
|
425
|
+
*
|
|
426
|
+
* core 3.0.0([2296] 装车单 S3,§DESIGN-V2 V2-②,codex 复审「异议」采纳):`TraceTurn.tokens` 是本服务自有
|
|
427
|
+
* 的投影契约,不是 wire verbatim——`in` 保持它一直以来的**总量**语义,3.0.0 起改取 `totalInputTokens`
|
|
428
|
+
* (归一化总输入含 cache)优先,`inputTokens` 回落(旧帧/尚未升级的 core 下 `totalInputTokens` 缺席时,
|
|
429
|
+
* `in` 落回旧值,读端零伤)。`uncachedIn` 只在**能确认自己踩在新契约帧上**(`totalInputTokens` 在场)时
|
|
430
|
+
* 才发——旧帧的 `inputTokens` 是族依赖的旧语义,标成「uncachedIn(cache-MISS)」会是谎言。 */
|
|
400
431
|
export function toContractTokens(usage) {
|
|
401
432
|
if (!usage || typeof usage !== "object")
|
|
402
433
|
return undefined;
|
|
403
434
|
const u = usage;
|
|
404
|
-
if (u.inputTokens == null && u.outputTokens == null)
|
|
435
|
+
if (u.inputTokens == null && u.totalInputTokens == null && u.outputTokens == null)
|
|
405
436
|
return undefined;
|
|
406
437
|
return {
|
|
407
|
-
in: u.inputTokens ?? 0,
|
|
438
|
+
in: u.totalInputTokens ?? u.inputTokens ?? 0,
|
|
408
439
|
out: u.outputTokens ?? 0,
|
|
440
|
+
...(u.totalInputTokens != null && u.inputTokens != null ? { uncachedIn: u.inputTokens } : {}),
|
|
409
441
|
...(u.cacheReadTokens != null ? { cacheRead: u.cacheReadTokens } : {}),
|
|
410
442
|
...(u.cacheWriteTokens != null ? { cacheWrite: u.cacheWriteTokens } : {}),
|
|
411
443
|
};
|
|
@@ -40,6 +40,10 @@ export interface UsageModelEntry {
|
|
|
40
40
|
outputTokens?: number;
|
|
41
41
|
cacheReadTokens?: number;
|
|
42
42
|
cacheWriteTokens?: number;
|
|
43
|
+
/** S10(core 3.0 计量语义统一,§DESIGN-V2 V2-⑤)口径标记:`"uncached-components-v1"` = 三个输入键是
|
|
44
|
+
* 分量制(互不重叠,本文件的 tokensIn 求和公式正确)。缺席/别的值 = 口径不可分辨(存量行可能把
|
|
45
|
+
* cache 计入 inputTokens,求和会双算)⇒ fold 侧标 {@link UsageTotals.estimated}。 */
|
|
46
|
+
usageBasis?: string;
|
|
43
47
|
/** 整数 micro-USD。**缺席 = 未知**,不是 0(SDK `ModelUsageDelta` 同款单轨契约)。注意在场的 `0` 仍有
|
|
44
48
|
* 歧义(引擎无价目表时恒发 0),那层消歧在部署面 `capabilities.pricingConfigured`——裁定见 budget.ts。 */
|
|
45
49
|
costMicroUsd?: number;
|
|
@@ -49,9 +53,11 @@ export interface UsageTotals {
|
|
|
49
53
|
tokensIn: number;
|
|
50
54
|
tokensOut: number;
|
|
51
55
|
costUsd: number;
|
|
52
|
-
/**
|
|
53
|
-
* ②有 per-model echo 行**缺数值键**(该键按 0 计入,总量偏低;成本键缺席时尤其:少算 ≠ 免费)
|
|
54
|
-
*
|
|
56
|
+
/** 本聚合的口径**不完整**——三种来源:①有行走了 stats.tokens fallback(无 per-model echo,token 数为估算);
|
|
57
|
+
* ②有 per-model echo 行**缺数值键**(该键按 0 计入,总量偏低;成本键缺席时尤其:少算 ≠ 免费);
|
|
58
|
+
* ③(S10/V2-⑤)有行的 {@link UsageModelEntry.usageBasis} 缺席或非本读端认识的分量制——存量行某族
|
|
59
|
+
* 历史上把 cache 计入 inputTokens,tokensIn 求和会**双算**,口径不可分辨。
|
|
60
|
+
* 三者都只表达「别把这个总数当精确值」,消费端一视同仁。 */
|
|
55
61
|
estimated: boolean;
|
|
56
62
|
}
|
|
57
63
|
/** GET /v1/usage/summary — 窗内总量。 */
|
package/dist/usage-analytics.js
CHANGED
|
@@ -36,6 +36,10 @@ function foldModelEntry(t, d) {
|
|
|
36
36
|
t.costUsd += (cost ?? 0) / 1e6;
|
|
37
37
|
if (input === undefined || output === undefined || cacheRead === undefined || cacheWrite === undefined || cost === undefined)
|
|
38
38
|
t.estimated = true;
|
|
39
|
+
// S10/V2-⑤:上面这条 tokensIn 求和公式只在「分量制」行上正确。缺标记(存量行,某族历史上 inputTokens
|
|
40
|
+
// 含 cache ⇒ 双算)或本读端不认识的标记值(将来别的口径)⇒ 走既有不精确通道,不显示为精确值。
|
|
41
|
+
if (d.usageBasis !== "uncached-components-v1")
|
|
42
|
+
t.estimated = true;
|
|
39
43
|
}
|
|
40
44
|
function foldRow(t, row) {
|
|
41
45
|
t.tasks += 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sema-agent/server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0",
|
|
4
4
|
"description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "BUSL-1.1",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"build:binary:run-local:darwin-arm64": "bun build --compile --target=bun-darwin-arm64 src/run-local.ts --outfile dist/run-local-darwin-arm64"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@sema-agent/core": "^
|
|
57
|
+
"@sema-agent/core": "^3.0.0",
|
|
58
58
|
"@sema-agent/registry-core": "^0.12.0",
|
|
59
59
|
"e2b": "^2.28.0",
|
|
60
60
|
"libsodium-wrappers": "^0.8.4",
|