oasis_test_v2 2.2.31 → 2.2.32

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.
Files changed (2) hide show
  1. package/dist/index.js +579 -325
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1295,11 +1295,12 @@ var init_chat_session = __esm({
1295
1295
  });
1296
1296
 
1297
1297
  // ../contract/src/live-protocol.ts
1298
- var NORMALIZED_PROVIDER_EVENT_VERSION;
1298
+ var NORMALIZED_PROVIDER_EVENT_VERSION, SUBAGENT_TASK_CODE;
1299
1299
  var init_live_protocol = __esm({
1300
1300
  "../contract/src/live-protocol.ts"() {
1301
1301
  "use strict";
1302
1302
  NORMALIZED_PROVIDER_EVENT_VERSION = 1;
1303
+ SUBAGENT_TASK_CODE = "subagent_task";
1303
1304
  }
1304
1305
  });
1305
1306
 
@@ -23127,14 +23128,132 @@ var init_filter_extra_args = __esm({
23127
23128
  });
23128
23129
 
23129
23130
  // ../adapters/src/claude-code/index.ts
23130
- function createClaudeLiveParserState(streamId) {
23131
+ function createClaudeLiveParserState(streamId, opts = {}) {
23131
23132
  return {
23132
23133
  streamId,
23133
23134
  turnNumber: 0,
23134
23135
  turnId: `claude-turn:${streamId}:0`,
23135
- blockIds: /* @__PURE__ */ new Map()
23136
+ blockIds: /* @__PURE__ */ new Map(),
23137
+ agentTasks: /* @__PURE__ */ new Map(),
23138
+ subagentCalls: /* @__PURE__ */ new Map(),
23139
+ ...opts.readSubagentUsage ? { readSubagentUsage: opts.readSubagentUsage } : {}
23136
23140
  };
23137
23141
  }
23142
+ function numberOf(value2) {
23143
+ return typeof value2 === "number" && Number.isFinite(value2) ? value2 : void 0;
23144
+ }
23145
+ function subagentCallOf(message) {
23146
+ const id = stringOf(message?.id);
23147
+ const usage = parseResultUsage(message?.usage);
23148
+ if (!id || !usage) return void 0;
23149
+ return {
23150
+ id,
23151
+ call: {
23152
+ model: stringOf(message?.model) ?? "unknown",
23153
+ tokens: {
23154
+ inputTokens: usage.input ?? 0,
23155
+ outputTokens: usage.output ?? 0,
23156
+ cacheReadTokens: usage.cacheRead ?? 0,
23157
+ cacheCreationTokens: usage.cacheCreation ?? 0
23158
+ }
23159
+ }
23160
+ };
23161
+ }
23162
+ function recordSubagentCall(calls, id, call) {
23163
+ const prev = calls.get(id);
23164
+ calls.set(id, prev ? {
23165
+ model: call.model,
23166
+ tokens: {
23167
+ inputTokens: Math.max(prev.tokens.inputTokens, call.tokens.inputTokens),
23168
+ outputTokens: Math.max(prev.tokens.outputTokens, call.tokens.outputTokens),
23169
+ cacheReadTokens: Math.max(prev.tokens.cacheReadTokens, call.tokens.cacheReadTokens),
23170
+ cacheCreationTokens: Math.max(prev.tokens.cacheCreationTokens, call.tokens.cacheCreationTokens)
23171
+ }
23172
+ } : call);
23173
+ }
23174
+ function byModelOf(calls) {
23175
+ const out = {};
23176
+ for (const { model, tokens } of calls.values()) {
23177
+ const acc = out[model] ?? (out[model] = { inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0 });
23178
+ acc.inputTokens += tokens.inputTokens;
23179
+ acc.outputTokens += tokens.outputTokens;
23180
+ acc.cacheReadTokens += tokens.cacheReadTokens;
23181
+ acc.cacheCreationTokens += tokens.cacheCreationTokens;
23182
+ }
23183
+ return out;
23184
+ }
23185
+ function readSubagentTranscriptUsage(outputFile) {
23186
+ try {
23187
+ const stat3 = fs7.statSync(outputFile);
23188
+ if (!stat3.isFile() || stat3.size > SUBAGENT_TRANSCRIPT_MAX_BYTES) return void 0;
23189
+ const calls = /* @__PURE__ */ new Map();
23190
+ for (const line of fs7.readFileSync(outputFile, "utf8").split("\n")) {
23191
+ if (!line.includes('"assistant"') || !line.includes('"usage"')) continue;
23192
+ let parsed;
23193
+ try {
23194
+ parsed = JSON.parse(line);
23195
+ } catch {
23196
+ continue;
23197
+ }
23198
+ if (parsed.type !== "assistant") continue;
23199
+ const hit = subagentCallOf(objectOf(parsed.message));
23200
+ if (hit) recordSubagentCall(calls, hit.id, hit.call);
23201
+ }
23202
+ return calls.size > 0 ? byModelOf(calls) : void 0;
23203
+ } catch {
23204
+ return void 0;
23205
+ }
23206
+ }
23207
+ function subagentTaskEvents(parsed, state) {
23208
+ const taskId = stringOf(parsed.task_id);
23209
+ if (!taskId) return [];
23210
+ const emit = (toolUseId, task) => [
23211
+ liveEvent(state, `${SUBAGENT_TASK_CODE}:${toolUseId}:${task.status}`, "control", "completed", {
23212
+ code: SUBAGENT_TASK_CODE,
23213
+ toolCallId: toolUseId,
23214
+ task: jsonOf(task)
23215
+ })
23216
+ ];
23217
+ if (parsed.subtype === "task_started") {
23218
+ const toolUseId = stringOf(parsed.tool_use_id);
23219
+ if (parsed.task_type !== "local_agent" || !toolUseId) return [];
23220
+ const description = stringOf(parsed.description);
23221
+ const subagentType = stringOf(parsed.subagent_type);
23222
+ const info = {
23223
+ ...description ? { description } : {},
23224
+ ...subagentType ? { subagentType } : {},
23225
+ background: parsed.is_backgrounded === true
23226
+ };
23227
+ state.agentTasks.set(taskId, { toolUseId, ...info });
23228
+ return emit(toolUseId, { status: "running", ...info });
23229
+ }
23230
+ if (parsed.subtype === "task_notification") {
23231
+ const known = state.agentTasks.get(taskId);
23232
+ if (!known) return [];
23233
+ state.agentTasks.delete(taskId);
23234
+ const { toolUseId, ...info } = known;
23235
+ const usage = objectOf(parsed.usage);
23236
+ const summary = stringOf(parsed.summary);
23237
+ const totalTokens = numberOf(usage?.total_tokens);
23238
+ const toolUses = numberOf(usage?.tool_uses);
23239
+ const durationMs3 = numberOf(usage?.duration_ms);
23240
+ const outputFile = stringOf(parsed.output_file);
23241
+ const fromTranscript = outputFile ? state.readSubagentUsage?.(outputFile) : void 0;
23242
+ const streamCalls = state.subagentCalls.get(toolUseId);
23243
+ state.subagentCalls.delete(toolUseId);
23244
+ const tokenUsage = fromTranscript && Object.keys(fromTranscript).length > 0 ? { byModel: fromTranscript, source: "transcript" } : streamCalls?.size ? { byModel: byModelOf(streamCalls), source: "stream" } : void 0;
23245
+ return emit(toolUseId, {
23246
+ ...info,
23247
+ status: SUBAGENT_TERMINAL[stringOf(parsed.status) ?? ""] ?? "completed",
23248
+ ...summary ? { summary } : {},
23249
+ ...totalTokens !== void 0 ? { totalTokens } : {},
23250
+ ...toolUses !== void 0 ? { toolUses } : {},
23251
+ ...durationMs3 !== void 0 ? { durationMs: durationMs3 } : {},
23252
+ ...tokenUsage ? { usage: tokenUsage } : {}
23253
+ });
23254
+ }
23255
+ return [];
23256
+ }
23138
23257
  function objectOf(value2) {
23139
23258
  return value2 !== null && typeof value2 === "object" && !Array.isArray(value2) ? value2 : void 0;
23140
23259
  }
@@ -23167,6 +23286,18 @@ function normalizeClaudeLiveLine(line, state) {
23167
23286
  const failed = parsed.is_error === true || parsed.subtype === "error";
23168
23287
  return [liveEvent(state, itemId, "control", failed ? "turn_failed" : "turn_completed", jsonOf(parsed))];
23169
23288
  }
23289
+ if (parsed.type === "system") return subagentTaskEvents(parsed, state);
23290
+ if (parsed.type === "assistant") {
23291
+ const parent = stringOf(parsed.parent_tool_use_id);
23292
+ const hit = parent ? subagentCallOf(objectOf(parsed.message)) : void 0;
23293
+ if (parent && hit) {
23294
+ const calls = state.subagentCalls.get(parent) ?? /* @__PURE__ */ new Map();
23295
+ recordSubagentCall(calls, hit.id, hit.call);
23296
+ state.subagentCalls.set(parent, calls);
23297
+ }
23298
+ return [];
23299
+ }
23300
+ if ((parsed.type === "user" || parsed.type === "stream_event") && stringOf(parsed.parent_tool_use_id)) return [];
23170
23301
  if (parsed.type === "user") {
23171
23302
  const content3 = Array.isArray(objectOf(parsed.message)?.content) ? objectOf(parsed.message).content : [];
23172
23303
  return content3.flatMap((block) => {
@@ -23237,19 +23368,21 @@ function normalizeClaudeStreamLine(line) {
23237
23368
  const message = parsed["message"];
23238
23369
  const content3 = Array.isArray(message?.content) ? message.content : [];
23239
23370
  const out = [];
23371
+ const parentToolCallId = stringOf(parsed["parent_tool_use_id"]);
23372
+ const sub = parentToolCallId ? { parentToolCallId } : {};
23240
23373
  for (const block of content3) {
23241
23374
  switch (block["type"]) {
23242
23375
  case "text":
23243
- if (type === "assistant") out.push({ kind: "message", payload: { text: block["text"] } });
23376
+ if (type === "assistant") out.push({ kind: "message", payload: { text: block["text"], ...sub } });
23244
23377
  break;
23245
23378
  case "thinking":
23246
- out.push({ kind: "thought", payload: { text: block["thinking"] } });
23379
+ out.push({ kind: "thought", payload: { text: block["thinking"], ...sub } });
23247
23380
  break;
23248
23381
  case "tool_use":
23249
- out.push({ kind: "tool_use", payload: { id: block["id"], name: block["name"], input: block["input"] } });
23382
+ out.push({ kind: "tool_use", payload: { id: block["id"], name: block["name"], input: block["input"], ...sub } });
23250
23383
  break;
23251
23384
  case "tool_result":
23252
- out.push({ kind: "tool_result", payload: { tool_use_id: block["tool_use_id"], content: block["content"], is_error: block["is_error"] ?? false } });
23385
+ out.push({ kind: "tool_result", payload: { tool_use_id: block["tool_use_id"], content: block["content"], is_error: block["is_error"] ?? false, ...sub } });
23253
23386
  break;
23254
23387
  default:
23255
23388
  break;
@@ -23400,7 +23533,7 @@ function classifyExit(result) {
23400
23533
  if (status === 429 || status === 529) return "rate-limit";
23401
23534
  return "error";
23402
23535
  }
23403
- var import_node_child_process3, import_node_crypto8, fs7, path7, readline, CLAUDE_BLOCKED_FLAGS, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
23536
+ var import_node_child_process3, import_node_crypto8, fs7, path7, readline, CLAUDE_BLOCKED_FLAGS, SUBAGENT_TRANSCRIPT_MAX_BYTES, SUBAGENT_TERMINAL, ONE_SHOT_UNSAFE_TOOLS, ClaudeCodeAdapter;
23404
23537
  var init_claude_code = __esm({
23405
23538
  "../adapters/src/claude-code/index.ts"() {
23406
23539
  "use strict";
@@ -23415,6 +23548,7 @@ var init_claude_code = __esm({
23415
23548
  init_resume_classify();
23416
23549
  init_root_bypass();
23417
23550
  init_session_paths();
23551
+ init_src();
23418
23552
  init_transcript();
23419
23553
  init_filter_extra_args();
23420
23554
  CLAUDE_BLOCKED_FLAGS = /* @__PURE__ */ new Map([
@@ -23445,6 +23579,13 @@ var init_claude_code = __esm({
23445
23579
  ["--include-partial-messages", "boolean"]
23446
23580
  // 同上
23447
23581
  ]);
23582
+ SUBAGENT_TRANSCRIPT_MAX_BYTES = 32 * 1024 * 1024;
23583
+ SUBAGENT_TERMINAL = {
23584
+ completed: "completed",
23585
+ failed: "failed",
23586
+ killed: "stopped",
23587
+ stopped: "stopped"
23588
+ };
23448
23589
  ONE_SHOT_UNSAFE_TOOLS = ["ScheduleWakeup", "CronCreate", "CronDelete", "CronList"];
23449
23590
  ClaudeCodeAdapter = class {
23450
23591
  constructor(opts = {}) {
@@ -23670,7 +23811,7 @@ var init_claude_code = __esm({
23670
23811
  const usageAcc = newClaudeUsageAcc();
23671
23812
  let sessionModel;
23672
23813
  const outputCbs = [];
23673
- const liveParser = createClaudeLiveParserState(`claude-live:${id}`);
23814
+ const liveParser = createClaudeLiveParserState(`claude-live:${id}`, { readSubagentUsage: readSubagentTranscriptUsage });
23674
23815
  if (this.opts.streamJson) {
23675
23816
  const rl = readline.createInterface({ input: child.stdout });
23676
23817
  rl.on("line", (line) => {
@@ -28919,6 +29060,14 @@ var init_keys = __esm({
28919
29060
  });
28920
29061
 
28921
29062
  // ../adapters/src/_core/normalizer/normalizer.ts
29063
+ function parentToolCallIdOf(payload) {
29064
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return void 0;
29065
+ const id = payload.parentToolCallId;
29066
+ return typeof id === "string" && id !== "" ? id : void 0;
29067
+ }
29068
+ function textBlocksOf(blocks) {
29069
+ return blocks.map((b2) => b2 && typeof b2 === "object" && typeof b2.text === "string" ? b2.text : "").filter(Boolean).join("\n");
29070
+ }
28922
29071
  var ProviderStreamNormalizer;
28923
29072
  var init_normalizer = __esm({
28924
29073
  "../adapters/src/_core/normalizer/normalizer.ts"() {
@@ -28955,6 +29104,8 @@ var init_normalizer = __esm({
28955
29104
  producedAny = false;
28956
29105
  /** Receipts wait for a whole output block; they never split its bytes. */
28957
29106
  pendingInputs = /* @__PURE__ */ new Map();
29107
+ /** 子代理正文/思考块的序号(它们没有 provider id)。严格递增、turn 收尾清零。 */
29108
+ subagentBlocks = 0;
28958
29109
  constructor(opts) {
28959
29110
  this.providerName = opts.providerName;
28960
29111
  this.hasLiveProtocol = opts.hasLiveProtocol === true;
@@ -28975,6 +29126,11 @@ var init_normalizer = __esm({
28975
29126
  }
28976
29127
  /** 遥测事件。 */
28977
29128
  onTelemetry(event) {
29129
+ const parentToolCallId = parentToolCallIdOf(event.payload);
29130
+ if (parentToolCallId) {
29131
+ this.subagentItem(event, parentToolCallId);
29132
+ return;
29133
+ }
28978
29134
  if (this.hasLiveProtocol && event.kind !== "tool_use") return;
28979
29135
  if (event.kind === "message" && this.sawTextOutput) return;
28980
29136
  const part = chatPartFromTrajectoryEvent(event);
@@ -29008,6 +29164,17 @@ var init_normalizer = __esm({
29008
29164
  this.closeOpen("completed");
29009
29165
  return;
29010
29166
  }
29167
+ if (event.itemType === "control" && payload.code === SUBAGENT_TASK_CODE && typeof payload.toolCallId === "string" && payload.task && typeof payload.task === "object") {
29168
+ this.emitItem({
29169
+ key: providerItemKey(this.providerName, event.itemId),
29170
+ kind: "control",
29171
+ role: "assistant",
29172
+ origin: "live",
29173
+ opShape: { op: "set_status", status: "completed" },
29174
+ attrs: { code: SUBAGENT_TASK_CODE, toolCallId: payload.toolCallId, task: payload.task }
29175
+ });
29176
+ return;
29177
+ }
29011
29178
  this.turnId = event.turnId || this.turnId;
29012
29179
  if (event.operation === "turn_completed" || event.operation === "turn_failed") {
29013
29180
  this.turnFinished(event.operation === "turn_failed" ? "failed" : "completed");
@@ -29041,11 +29208,52 @@ var init_normalizer = __esm({
29041
29208
  }
29042
29209
  if (event.operation === "completed" && event.itemType === "tool" && event.payload !== void 0) {
29043
29210
  const id = typeof payload.tool_use_id === "string" ? payload.tool_use_id : event.itemId;
29044
- const output = typeof payload.content === "string" ? payload.content : typeof payload.output === "string" ? payload.output : "";
29211
+ const output = typeof payload.content === "string" ? payload.content : Array.isArray(payload.content) ? textBlocksOf(payload.content) : typeof payload.output === "string" ? payload.output : "";
29045
29212
  this.toolResult("live", id, void 0, output, payload.is_error === true);
29046
29213
  return;
29047
29214
  }
29048
29215
  }
29216
+ /**
29217
+ * 子代理内部的一块(带 parentToolCallId 的 telemetry)。子代理没有 partial stream,每块到达时
29218
+ * 就是完整的,所以不走散文的「开一段 → 续写」,也不过认领闸、不关主线程开着的那段——后台子代理
29219
+ * 与主线程并发时,主线程正文不能被它切成几截。
29220
+ */
29221
+ subagentItem(event, parentToolCallId) {
29222
+ const part = chatPartFromTrajectoryEvent(event);
29223
+ if (!part) return;
29224
+ const sub = { parentToolCallId };
29225
+ if (part.type === "text" || part.type === "thinking") {
29226
+ const kind = part.type;
29227
+ const key = providerItemKey(this.providerName, `sub:${parentToolCallId}:${kind}`, this.subagentBlocks++);
29228
+ this.emitItem({ key, kind, role: "assistant", origin: "telemetry", opShape: { op: "set_text", text: part.text ?? "" }, attrs: sub });
29229
+ this.emitItem({ key, kind, role: "assistant", origin: "telemetry", opShape: { op: "set_status", status: "completed" }, attrs: sub });
29230
+ return;
29231
+ }
29232
+ if (part.type === "tool_use") {
29233
+ const id = part.toolCallId;
29234
+ this.emitItem({
29235
+ key: id ? providerItemKey(this.providerName, `tool_use:${id}`) : this.nextFallbackKey("tool_call"),
29236
+ kind: "tool_call",
29237
+ role: "assistant",
29238
+ origin: "telemetry",
29239
+ opShape: { op: "patch_input", input: part.input ?? null },
29240
+ attrs: { toolName: part.toolName ?? "tool", ...id ? { toolCallId: id } : {}, ...sub }
29241
+ });
29242
+ return;
29243
+ }
29244
+ if (part.type === "tool_result") {
29245
+ const id = part.toolCallId;
29246
+ const output = part.output ?? "";
29247
+ this.emitItem({
29248
+ key: id ? providerItemKey(this.providerName, `tool_result:${id}`) : this.nextFallbackKey("tool_result"),
29249
+ kind: "tool_result",
29250
+ role: "assistant",
29251
+ origin: "telemetry",
29252
+ opShape: { op: "set_text", text: output },
29253
+ attrs: { output, isError: part.isError === true, ...id ? { toolCallId: id } : {}, ...sub }
29254
+ });
29255
+ }
29256
+ }
29049
29257
  // ── turn 生命周期 ──────────────────────────────────────────────────────────
29050
29258
  /** turn 收尾:关掉开着的散文、清兜底 index、报一条 turn 信号(服务端据此清 itemKeyMap)。 */
29051
29259
  turnFinished(signal) {
@@ -29057,6 +29265,7 @@ var init_normalizer = __esm({
29057
29265
  this.closedKeys.clear();
29058
29266
  this.sawTextOutput = false;
29059
29267
  this.producedAny = false;
29268
+ this.subagentBlocks = 0;
29060
29269
  if (!produced) return;
29061
29270
  this.emit({ normalizedVersion: NORMALIZED_PROVIDER_EVENT_VERSION, type: "turn", turnId: this.turnId, signal });
29062
29271
  }
@@ -29282,6 +29491,341 @@ var init_src6 = __esm({
29282
29491
  }
29283
29492
  });
29284
29493
 
29494
+ // ../server/src/domains/actors/model-pricing.ts
29495
+ function lookupInMap(map, prefixKeys, candidates) {
29496
+ for (const candidate of candidates) {
29497
+ const exact = map instanceof Map ? map.get(candidate) : map[candidate];
29498
+ if (exact) return exact;
29499
+ }
29500
+ const prefix = prefixKeys.find((p2) => candidates.some((candidate) => candidate.startsWith(`${p2}-`) || candidate.startsWith(`${p2}:`)));
29501
+ if (!prefix) return null;
29502
+ return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
29503
+ }
29504
+ function stripBracketTier(model) {
29505
+ return model.replace(/\[[^\]]*\]/g, "").trim();
29506
+ }
29507
+ function modelCandidates(model) {
29508
+ const normalized4 = model.trim().toLowerCase();
29509
+ if (!normalized4) return [];
29510
+ const base = [
29511
+ normalized4,
29512
+ normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
29513
+ normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
29514
+ ].filter(Boolean);
29515
+ const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
29516
+ return [...base, ...new Set(stripped)];
29517
+ }
29518
+ function setModelPriceCacheLoader(loader) {
29519
+ cacheLoader = loader;
29520
+ }
29521
+ async function warmModelPriceCache() {
29522
+ if (!cacheLoader) return 0;
29523
+ try {
29524
+ const next = await cacheLoader();
29525
+ dbPrices = next;
29526
+ dbPrefixKeys = [...next.keys()].sort((a, b2) => b2.length - a.length);
29527
+ cacheLoadedAt = Date.now();
29528
+ return next.size;
29529
+ } catch (err) {
29530
+ console.warn(`[model-pricing] \u9884\u70ED\u4EF7\u683C\u7F13\u5B58\u5931\u8D25\uFF0C\u6CBF\u7528\u9759\u6001\u5FEB\u7167\uFF1A${String(err)}`);
29531
+ return 0;
29532
+ }
29533
+ }
29534
+ function maybeRefreshCache() {
29535
+ if (!cacheLoader) return;
29536
+ if (dbPrices && Date.now() - cacheLoadedAt < CACHE_TTL_MS2) return;
29537
+ if (cacheRefreshInFlight) return;
29538
+ cacheRefreshInFlight = warmModelPriceCache().catch(() => 0).then(() => {
29539
+ cacheRefreshInFlight = null;
29540
+ });
29541
+ }
29542
+ function priceForModel(model) {
29543
+ if (!model) return null;
29544
+ const candidates = modelCandidates(model);
29545
+ if (candidates.length === 0) return null;
29546
+ maybeRefreshCache();
29547
+ if (dbPrices && dbPrices.size > 0) {
29548
+ const fromDb = lookupInMap(dbPrices, dbPrefixKeys, candidates);
29549
+ if (fromDb) return fromDb;
29550
+ }
29551
+ return lookupInMap(MODEL_PRICES, STATIC_PREFIX_PRICES, candidates);
29552
+ }
29553
+ function estimateUsageCostBreakdown(model, usage) {
29554
+ const price = priceForModel(model);
29555
+ if (!price) return null;
29556
+ const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
29557
+ const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
29558
+ const cacheReadTokens = Math.max(usage.cacheReadTokens ?? 0, 0);
29559
+ const cacheCreationTokens = Math.max(usage.cacheCreationTokens ?? 0, 0);
29560
+ const cacheReadPrice = price.cachedInputPerMillion ?? price.inputPerMillion * 0.1;
29561
+ return {
29562
+ currency: price.currency,
29563
+ inputMicros: Math.round(inputTokens * price.inputPerMillion / 1e6 * 1e6),
29564
+ cacheMicros: Math.round(
29565
+ (cacheCreationTokens * price.inputPerMillion + cacheReadTokens * cacheReadPrice) / 1e6 * 1e6
29566
+ ),
29567
+ outputMicros: Math.round(outputTokens * price.outputPerMillion / 1e6 * 1e6)
29568
+ };
29569
+ }
29570
+ function estimateUsageCost(model, usage) {
29571
+ const breakdown = estimateUsageCostBreakdown(model, usage);
29572
+ if (!breakdown) return null;
29573
+ return {
29574
+ currency: breakdown.currency,
29575
+ micros: breakdown.inputMicros + breakdown.cacheMicros + breakdown.outputMicros
29576
+ };
29577
+ }
29578
+ function displayCostUsdMicros(model, usage) {
29579
+ const stored = Math.max(usage.costUsdMicros ?? 0, 0);
29580
+ if (stored > 0) return stored;
29581
+ const estimated = estimateUsageCost(model, usage);
29582
+ if (!estimated || estimated.micros <= 0) return 0;
29583
+ if (estimated.currency === "USD") return estimated.micros;
29584
+ return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
29585
+ }
29586
+ var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
29587
+ var init_model_pricing = __esm({
29588
+ "../server/src/domains/actors/model-pricing.ts"() {
29589
+ "use strict";
29590
+ init_src();
29591
+ FOREIGN_MODEL_MARKUP = 1.08;
29592
+ usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
29593
+ currency: "USD",
29594
+ inputPerMillion: inputPerMillion * FOREIGN_MODEL_MARKUP,
29595
+ outputPerMillion: outputPerMillion * FOREIGN_MODEL_MARKUP,
29596
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion: cachedInputPerMillion * FOREIGN_MODEL_MARKUP } : {}
29597
+ });
29598
+ cny = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
29599
+ currency: "CNY",
29600
+ inputPerMillion,
29601
+ outputPerMillion,
29602
+ ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion } : {}
29603
+ });
29604
+ MODEL_PRICES = {
29605
+ "gpt-5.5": usd(5, 30, 0.5),
29606
+ "gpt-5.5-pro": usd(30, 180),
29607
+ "chat-latest": usd(5, 30, 0.5),
29608
+ "gpt-5.4-mini": usd(0.75, 4.5, 0.075),
29609
+ "gpt-5.4-nano": usd(0.2, 1.25, 0.02),
29610
+ "gpt-5.4": usd(2.5, 15, 0.25),
29611
+ "gpt-5.4-pro": usd(30, 180),
29612
+ "gpt-5.3-codex": usd(1.75, 14, 0.175),
29613
+ "gpt-5.2": usd(1.75, 14, 0.175),
29614
+ "gpt-5.2-chat-latest": usd(1.75, 14, 0.175),
29615
+ "gpt-5.2-pro": usd(21, 168),
29616
+ "gpt-5.2-codex": usd(1.75, 14, 0.175),
29617
+ "gpt-5.1": usd(1.25, 10, 0.125),
29618
+ "gpt-5.1-chat-latest": usd(1.25, 10, 0.125),
29619
+ "gpt-5.1-codex": usd(1.25, 10, 0.125),
29620
+ "gpt-5.1-codex-max": usd(1.25, 10, 0.125),
29621
+ "gpt-5.1-codex-mini": usd(0.25, 2, 0.025),
29622
+ "gpt-5-pro": usd(15, 120),
29623
+ "gpt-5-codex": usd(1.25, 10, 0.125),
29624
+ "gpt-5-search-api": usd(1.25, 10, 0.125),
29625
+ "gpt-5": usd(1.25, 10, 0.125),
29626
+ "gpt-5-mini": usd(0.25, 2, 0.025),
29627
+ "gpt-5-nano": usd(0.05, 0.4, 5e-3),
29628
+ "gpt-5-chat-latest": usd(1.25, 10, 0.125),
29629
+ "gpt-4.1": usd(2, 8, 0.5),
29630
+ "gpt-4.1-2025-04-14": usd(2, 8, 0.5),
29631
+ "gpt-4.1-mini": usd(0.4, 1.6, 0.1),
29632
+ "gpt-4.1-mini-2025-04-14": usd(0.4, 1.6, 0.1),
29633
+ "gpt-4.1-nano": usd(0.1, 0.4, 0.025),
29634
+ "gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
29635
+ "o4-mini": usd(1.1, 4.4, 0.275),
29636
+ // o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
29637
+ // 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
29638
+ "o3": usd(2, 8),
29639
+ "o3-mini": usd(1.1, 4.4),
29640
+ "claude-fable-5-1": usd(10, 50),
29641
+ "claude-fable-5": usd(10, 50),
29642
+ "claude-mythos-5-1": usd(10, 50),
29643
+ "claude-mythos-5": usd(10, 50),
29644
+ // claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
29645
+ // (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
29646
+ "claude-opus-5": usd(5, 25),
29647
+ "claude-opus-4-8": usd(5, 25),
29648
+ "claude-opus-4-7": usd(5, 25),
29649
+ "claude-sonnet-5": usd(2, 10),
29650
+ "claude-sonnet-4-6": usd(3, 15),
29651
+ "claude-opus-4-6": usd(5, 25),
29652
+ "claude-opus-4-5-20251101": usd(5, 25),
29653
+ "claude-opus-4-5": usd(5, 25),
29654
+ "claude-sonnet-4-5-20250929": usd(3, 15),
29655
+ // 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
29656
+ // claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
29657
+ "claude-sonnet-4-5": usd(3, 15),
29658
+ "claude-opus-4-1-20250805": usd(15, 75),
29659
+ "claude-opus-4-20250514": usd(15, 75),
29660
+ "claude-sonnet-4-20250514": usd(3, 15),
29661
+ "claude-3-7-sonnet-20250219": usd(3, 15),
29662
+ "claude-3-5-sonnet-20241022": usd(3, 15),
29663
+ "claude-3-5-sonnet-20240620": usd(3, 15),
29664
+ "claude-3-sonnet-20240229": usd(3, 15),
29665
+ "claude-haiku-4-5-20251001": usd(1, 5),
29666
+ // codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
29667
+ // 但字符串对不上就查不到价 → 记 0。同价另起别名行。
29668
+ "claude-sonnet-4.6": usd(3, 15),
29669
+ "claude-opus-4.7": usd(5, 25),
29670
+ "gemini-3.1-pro": usd(2, 12),
29671
+ "gemini-3-pro-preview": usd(2, 12),
29672
+ // Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
29673
+ "claude-haiku-4-5": usd(1, 5),
29674
+ "gemini-3.5-flash": usd(1.5, 9),
29675
+ "gemini-3.1-pro-preview": usd(2, 12),
29676
+ "gemini-3.1-pro-preview-customtools": usd(2, 12),
29677
+ "gemini-3.1-flash-lite-preview": usd(0.25, 1.5),
29678
+ "gemini-3-flash-preview": usd(0.5, 3),
29679
+ "gemini-2.5-pro": usd(1.25, 10),
29680
+ "gemini-2.5-flash": usd(0.3, 2.5),
29681
+ "gemini-2.5-flash-lite": usd(0.1, 0.4),
29682
+ "gemini-2.5-flash-preview-09-2025": usd(0.3, 2.5),
29683
+ "gemini-2.0-flash": usd(0.1, 0.4),
29684
+ "deepseek-v4-flash": cny(1, 2, 0.02),
29685
+ "deepseek-v4-pro": cny(3, 6, 0.025),
29686
+ "deepseek-chat": cny(2, 3),
29687
+ "deepseek-reasoner": cny(2, 3),
29688
+ "deepseek-v3.2": cny(2, 3),
29689
+ "deepseek-coder": cny(2, 3),
29690
+ "deepseek-r1": cny(4, 16),
29691
+ "deepseek-r1-0528": cny(4, 16),
29692
+ "deepseek-v3": cny(2, 8),
29693
+ "deepseek-v3-0324": cny(2, 8),
29694
+ "deepseek-v3.1": cny(4, 12),
29695
+ "qwen3-max": cny(6, 24),
29696
+ "qwen3-max-preview": cny(6, 24),
29697
+ "qwen3-coder-plus": cny(4, 16),
29698
+ "qwen3-235b-a22b": cny(4, 40),
29699
+ "qwen3-32b": cny(2, 20),
29700
+ "qwen3-30b-a3b": cny(1.5, 15),
29701
+ "qwen3-14b": cny(1, 10),
29702
+ "qwen3-8b": cny(0.5, 5),
29703
+ "qwen3-4b": cny(0.3, 3),
29704
+ "qwen3-1.7b": cny(0.3, 3),
29705
+ "qwen3-0.6b": cny(0.3, 3),
29706
+ "qwq-plus": cny(1.6, 4),
29707
+ "qwq-plus-latest": cny(1.6, 4),
29708
+ "qwq-plus-2025-03-05": cny(1.6, 4),
29709
+ "kimi-k2.7-code": cny(6.5, 27),
29710
+ "kimi-k2.6": cny(6.5, 27),
29711
+ "kimi-k2.5": cny(4, 21),
29712
+ "kimi-k2-0905-preview": cny(4, 16),
29713
+ "kimi-k2-0711-preview": cny(4, 16),
29714
+ "kimi-k2-turbo-preview": cny(8, 58)
29715
+ };
29716
+ STATIC_PREFIX_PRICES = Object.keys(MODEL_PRICES).sort((a, b2) => b2.length - a.length);
29717
+ CACHE_TTL_MS2 = 5 * 6e4;
29718
+ dbPrices = null;
29719
+ dbPrefixKeys = [];
29720
+ cacheLoadedAt = 0;
29721
+ cacheLoader = null;
29722
+ cacheRefreshInFlight = null;
29723
+ }
29724
+ });
29725
+
29726
+ // ../server/src/domains/usage/accounting.ts
29727
+ function nonNegative(v2) {
29728
+ return Math.max(v2 ?? 0, 0);
29729
+ }
29730
+ function resolvedModel(model) {
29731
+ const trimmed = model?.trim();
29732
+ return trimmed ? trimmed : null;
29733
+ }
29734
+ function splitByWeight(total, w2) {
29735
+ const sum = w2.input + w2.cache + w2.output;
29736
+ if (total === 0) return { input: 0, cache: 0, output: 0 };
29737
+ if (sum <= 0) return { input: 0, cache: 0, output: total };
29738
+ const input = Math.round(total * w2.input / sum);
29739
+ const cache = Math.round(total * w2.cache / sum);
29740
+ return { input, cache, output: total - input - cache };
29741
+ }
29742
+ function toDisplayUsd(currency, micros) {
29743
+ return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
29744
+ }
29745
+ function accountUsage(model, usage) {
29746
+ const resolved = resolvedModel(model);
29747
+ if (!resolved || !usage) return null;
29748
+ const input = nonNegative(usage.inputTokens);
29749
+ const output = nonNegative(usage.outputTokens);
29750
+ const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
29751
+ const tokens = { input, cache, output, total: input + cache + output };
29752
+ const estimated = estimateUsageCostBreakdown(resolved, usage);
29753
+ const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
29754
+ const reported = Math.max(usage.costUsdMicros ?? 0, 0);
29755
+ if (reported > 0) {
29756
+ const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
29757
+ return {
29758
+ tokens,
29759
+ currency: "USD",
29760
+ amountMicros: reported,
29761
+ parts,
29762
+ displayUsdMicros: reported,
29763
+ displayParts: parts,
29764
+ source: "reported"
29765
+ };
29766
+ }
29767
+ if (estimated && weights) {
29768
+ const amount = weights.input + weights.cache + weights.output;
29769
+ const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
29770
+ return {
29771
+ tokens,
29772
+ currency: estimated.currency,
29773
+ amountMicros: amount,
29774
+ parts: weights,
29775
+ displayUsdMicros,
29776
+ displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
29777
+ source: "estimated"
29778
+ };
29779
+ }
29780
+ return {
29781
+ tokens,
29782
+ currency: "USD",
29783
+ amountMicros: 0,
29784
+ parts: { input: 0, cache: 0, output: 0 },
29785
+ displayUsdMicros: 0,
29786
+ displayParts: { input: 0, cache: 0, output: 0 },
29787
+ source: "unpriced"
29788
+ };
29789
+ }
29790
+ function isUnpriced(account) {
29791
+ return account.source === "unpriced" && account.tokens.total > 0;
29792
+ }
29793
+ var init_accounting = __esm({
29794
+ "../server/src/domains/usage/accounting.ts"() {
29795
+ "use strict";
29796
+ init_model_pricing();
29797
+ }
29798
+ });
29799
+
29800
+ // ../server/src/subagent-cost.ts
29801
+ function estimateSubagentCostUsdMicros(usage) {
29802
+ if (!usage) return void 0;
29803
+ const models = Object.entries(usage.byModel);
29804
+ if (models.length === 0) return void 0;
29805
+ let total = 0;
29806
+ for (const [model, tokens] of models) {
29807
+ const account = accountUsage(model, { ...tokens, costUsdMicros: null });
29808
+ if (!account || account.source !== "estimated") return void 0;
29809
+ total += account.displayUsdMicros;
29810
+ }
29811
+ return total;
29812
+ }
29813
+ function withSubagentCostEstimate(event) {
29814
+ if (event.type !== "item" || event.attrs?.code !== SUBAGENT_TASK_CODE) return event;
29815
+ const task = event.attrs.task;
29816
+ if (!task?.usage || task.estimatedCostUsdMicros !== void 0) return event;
29817
+ const estimatedCostUsdMicros = estimateSubagentCostUsdMicros(task.usage);
29818
+ if (estimatedCostUsdMicros === void 0) return event;
29819
+ return { ...event, attrs: { ...event.attrs, task: { ...task, estimatedCostUsdMicros } } };
29820
+ }
29821
+ var init_subagent_cost = __esm({
29822
+ "../server/src/subagent-cost.ts"() {
29823
+ "use strict";
29824
+ init_src();
29825
+ init_accounting();
29826
+ }
29827
+ });
29828
+
29285
29829
  // ../contract/src/chat-item.ts
29286
29830
  var CHAT_ITEM_TERMINAL_STATUSES, isTerminalChatItemStatus, CHAT_ITEM_META_LEGACY_CONTENT;
29287
29831
  var init_chat_item = __esm({
@@ -29305,8 +29849,9 @@ async function resolveChatItemsTurnId(items, messageId, fallback) {
29305
29849
  const existing = await items.listItemsByMessage(messageId).catch(() => []);
29306
29850
  return existing.length ? existing[existing.length - 1].turnId : fallback;
29307
29851
  }
29308
- function projectsIntoContent(kind, role, origin, telemetryTextInContent) {
29852
+ function projectsIntoContent(kind, role, origin, telemetryTextInContent, attrs = {}) {
29309
29853
  if (kind !== "text" || role !== "assistant") return false;
29854
+ if (attrs.parentToolCallId) return false;
29310
29855
  switch (origin) {
29311
29856
  case "stdout":
29312
29857
  case "live":
@@ -29322,6 +29867,7 @@ var init_chat_item_ledger = __esm({
29322
29867
  "../server/src/chat-item-ledger.ts"() {
29323
29868
  "use strict";
29324
29869
  init_src();
29870
+ init_subagent_cost();
29325
29871
  init_chat_item();
29326
29872
  import_node_crypto15 = require("node:crypto");
29327
29873
  CHAT_ITEMS_DOUBLE_WRITE_ENV = "OASIS_CHAT_ITEMS_DOUBLE_WRITE";
@@ -29422,7 +29968,8 @@ var init_chat_item_ledger = __esm({
29422
29968
  * 应用一条归一层事件。**这是 provider 正文进入系统的唯一入口**(ADR D4(b))——
29423
29969
  * `LiveChatRegistry.applyNormalizedEvent` 与两条恢复路都经它,别再开第二个口子。
29424
29970
  */
29425
- apply(event) {
29971
+ apply(raw) {
29972
+ const event = withSubagentCostEstimate(raw);
29426
29973
  if (event.type === "turn") {
29427
29974
  this.itemKeyMap.clear();
29428
29975
  this.lastTurnId = null;
@@ -29661,7 +30208,7 @@ var init_chat_item_ledger = __esm({
29661
30208
  // ── 内部:建行与应用 op ────────────────────────────────────────────────────
29662
30209
  createFrom(event) {
29663
30210
  const attrs = event.attrs ?? {};
29664
- const inContent = projectsIntoContent(event.kind, event.role, event.origin, this.telemetryTextInContent);
30211
+ const inContent = projectsIntoContent(event.kind, event.role, event.origin, this.telemetryTextInContent, attrs);
29665
30212
  const text5 = event.opShape.op === "append_text" || event.opShape.op === "set_text" ? event.opShape.text : "";
29666
30213
  const status = event.kind === "text" || event.kind === "thinking" ? "streaming" : "completed";
29667
30214
  const id = this.insert({
@@ -29733,25 +30280,33 @@ var init_chat_item_ledger = __esm({
29733
30280
  }
29734
30281
  /** 按 kind 折出落库 payload。**唯一一处** payload 形状定义,别在别处再拼一遍。 */
29735
30282
  payloadOf(row) {
30283
+ const parent = row.attrs.parentToolCallId ? { parentToolCallId: row.attrs.parentToolCallId } : {};
29736
30284
  switch (row.kind) {
29737
30285
  case "text":
29738
30286
  case "thinking":
29739
- return { text: row.text };
30287
+ return { text: row.text, ...parent };
29740
30288
  case "tool_call":
29741
30289
  return {
29742
30290
  toolName: row.attrs.toolName ?? "tool",
29743
30291
  ...row.attrs.toolCallId ? { toolCallId: row.attrs.toolCallId } : {},
29744
- input: row.attrs.input ?? null
30292
+ input: row.attrs.input ?? null,
30293
+ ...parent
29745
30294
  };
29746
30295
  case "tool_result":
29747
30296
  return {
29748
30297
  ...row.attrs.toolCallId ? { toolCallId: row.attrs.toolCallId } : {},
29749
30298
  ...row.attrs.toolName ? { toolName: row.attrs.toolName } : {},
29750
30299
  output: row.attrs.output ?? row.text,
29751
- ...row.attrs.isError === true ? { isError: true } : {}
30300
+ ...row.attrs.isError === true ? { isError: true } : {},
30301
+ ...parent
29752
30302
  };
29753
30303
  case "control":
29754
- return { code: row.attrs.code ?? "control", ...row.attrs.note ? { note: row.attrs.note } : {} };
30304
+ return {
30305
+ code: row.attrs.code ?? "control",
30306
+ ...row.attrs.note ? { note: row.attrs.note } : {},
30307
+ ...row.attrs.code === SUBAGENT_TASK_CODE && row.attrs.toolCallId ? { toolCallId: row.attrs.toolCallId } : {},
30308
+ ...row.attrs.code === SUBAGENT_TASK_CODE && row.attrs.task ? { task: row.attrs.task } : {}
30309
+ };
29755
30310
  }
29756
30311
  }
29757
30312
  insert(input) {
@@ -160569,7 +161124,9 @@ function v3StartedPayload(role, attrs, itemType, kind) {
160569
161124
  if (attrs?.input !== void 0) base.input = attrs.input;
160570
161125
  if (attrs?.output !== void 0) base.output = attrs.output;
160571
161126
  if (attrs?.isError !== void 0) base.isError = attrs.isError;
161127
+ if (attrs?.parentToolCallId) base.parentToolCallId = attrs.parentToolCallId;
160572
161128
  if (itemType === "control" && kind === "control" && attrs?.code) base.code = attrs.code;
161129
+ if (itemType === "control" && attrs?.task) base.task = attrs.task;
160573
161130
  return base;
160574
161131
  }
160575
161132
  var DEFAULT_BUFFER_MAX, DEFAULT_GRACE_MS, DEFAULT_LIVE_COALESCE_MS, DEFAULT_LIVE_COALESCE_MAX_CHUNKS, DEFAULT_LIVE_COALESCE_MAX_CHARS, LiveChatRegistry;
@@ -160577,6 +161134,7 @@ var init_live_chat = __esm({
160577
161134
  "../server/src/live-chat.ts"() {
160578
161135
  "use strict";
160579
161136
  init_chat_item_ledger();
161137
+ init_subagent_cost();
160580
161138
  DEFAULT_BUFFER_MAX = 4e3;
160581
161139
  DEFAULT_GRACE_MS = 6e4;
160582
161140
  DEFAULT_LIVE_COALESCE_MS = 24;
@@ -160744,7 +161302,8 @@ var init_live_chat = __esm({
160744
161302
  *
160745
161303
  * 账本坏掉不影响对话;v3 emit 抛异常也不影响 v2 通路与账本落盘——两条独立通路。
160746
161304
  */
160747
- applyNormalizedEvent(turn, event) {
161305
+ applyNormalizedEvent(turn, raw) {
161306
+ const event = withSubagentCostEstimate(raw);
160748
161307
  if (event.type === "item" && event.kind === "control" && event.attrs?.code === "input_consumed") {
160749
161308
  const key = event.attrs.clientMessageKey;
160750
161309
  if (key) turn.inputReceipts.get(key)?.consume();
@@ -207537,6 +208096,7 @@ ${composed}`;
207537
208096
  session.onTelemetry?.((event) => {
207538
208097
  if (hasLiveProtocol && event.kind !== "tool_use") return;
207539
208098
  if (event.kind === "message" && sawTextOutput) return;
208099
+ if (typeof event.payload?.parentToolCallId === "string") return;
207540
208100
  if (liveProtocol && live && !hasLiveProtocol) {
207541
208101
  const payload = event.payload && typeof event.payload === "object" && !Array.isArray(event.payload) ? event.payload : {};
207542
208102
  const itemId = event.kind === "tool_use" ? typeof payload.id === "string" ? payload.id : `${liveTurnId}:tool` : event.kind === "tool_result" ? typeof payload.tool_use_id === "string" ? payload.tool_use_id : `${liveTurnId}:tool` : event.kind === "thought" ? `${liveTurnId}:thinking` : `${liveTurnId}:message`;
@@ -211986,312 +212546,6 @@ var init_uncategorized_project = __esm({
211986
212546
  }
211987
212547
  });
211988
212548
 
211989
- // ../server/src/domains/actors/model-pricing.ts
211990
- function lookupInMap(map, prefixKeys, candidates) {
211991
- for (const candidate of candidates) {
211992
- const exact = map instanceof Map ? map.get(candidate) : map[candidate];
211993
- if (exact) return exact;
211994
- }
211995
- const prefix = prefixKeys.find((p2) => candidates.some((candidate) => candidate.startsWith(`${p2}-`) || candidate.startsWith(`${p2}:`)));
211996
- if (!prefix) return null;
211997
- return map instanceof Map ? map.get(prefix) ?? null : map[prefix] ?? null;
211998
- }
211999
- function stripBracketTier(model) {
212000
- return model.replace(/\[[^\]]*\]/g, "").trim();
212001
- }
212002
- function modelCandidates(model) {
212003
- const normalized4 = model.trim().toLowerCase();
212004
- if (!normalized4) return [];
212005
- const base = [
212006
- normalized4,
212007
- normalized4.includes(":") ? normalized4.slice(normalized4.lastIndexOf(":") + 1) : "",
212008
- normalized4.includes("/") ? normalized4.slice(normalized4.lastIndexOf("/") + 1) : ""
212009
- ].filter(Boolean);
212010
- const stripped = base.map(stripBracketTier).filter((m2) => m2 && !base.includes(m2));
212011
- return [...base, ...new Set(stripped)];
212012
- }
212013
- function setModelPriceCacheLoader(loader) {
212014
- cacheLoader = loader;
212015
- }
212016
- async function warmModelPriceCache() {
212017
- if (!cacheLoader) return 0;
212018
- try {
212019
- const next = await cacheLoader();
212020
- dbPrices = next;
212021
- dbPrefixKeys = [...next.keys()].sort((a, b2) => b2.length - a.length);
212022
- cacheLoadedAt = Date.now();
212023
- return next.size;
212024
- } catch (err) {
212025
- console.warn(`[model-pricing] \u9884\u70ED\u4EF7\u683C\u7F13\u5B58\u5931\u8D25\uFF0C\u6CBF\u7528\u9759\u6001\u5FEB\u7167\uFF1A${String(err)}`);
212026
- return 0;
212027
- }
212028
- }
212029
- function maybeRefreshCache() {
212030
- if (!cacheLoader) return;
212031
- if (dbPrices && Date.now() - cacheLoadedAt < CACHE_TTL_MS2) return;
212032
- if (cacheRefreshInFlight) return;
212033
- cacheRefreshInFlight = warmModelPriceCache().catch(() => 0).then(() => {
212034
- cacheRefreshInFlight = null;
212035
- });
212036
- }
212037
- function priceForModel(model) {
212038
- if (!model) return null;
212039
- const candidates = modelCandidates(model);
212040
- if (candidates.length === 0) return null;
212041
- maybeRefreshCache();
212042
- if (dbPrices && dbPrices.size > 0) {
212043
- const fromDb = lookupInMap(dbPrices, dbPrefixKeys, candidates);
212044
- if (fromDb) return fromDb;
212045
- }
212046
- return lookupInMap(MODEL_PRICES, STATIC_PREFIX_PRICES, candidates);
212047
- }
212048
- function estimateUsageCostBreakdown(model, usage) {
212049
- const price = priceForModel(model);
212050
- if (!price) return null;
212051
- const inputTokens = Math.max(usage.inputTokens ?? 0, 0);
212052
- const outputTokens = Math.max(usage.outputTokens ?? 0, 0);
212053
- const cacheReadTokens = Math.max(usage.cacheReadTokens ?? 0, 0);
212054
- const cacheCreationTokens = Math.max(usage.cacheCreationTokens ?? 0, 0);
212055
- const cacheReadPrice = price.cachedInputPerMillion ?? price.inputPerMillion * 0.1;
212056
- return {
212057
- currency: price.currency,
212058
- inputMicros: Math.round(inputTokens * price.inputPerMillion / 1e6 * 1e6),
212059
- cacheMicros: Math.round(
212060
- (cacheCreationTokens * price.inputPerMillion + cacheReadTokens * cacheReadPrice) / 1e6 * 1e6
212061
- ),
212062
- outputMicros: Math.round(outputTokens * price.outputPerMillion / 1e6 * 1e6)
212063
- };
212064
- }
212065
- function estimateUsageCost(model, usage) {
212066
- const breakdown = estimateUsageCostBreakdown(model, usage);
212067
- if (!breakdown) return null;
212068
- return {
212069
- currency: breakdown.currency,
212070
- micros: breakdown.inputMicros + breakdown.cacheMicros + breakdown.outputMicros
212071
- };
212072
- }
212073
- function displayCostUsdMicros(model, usage) {
212074
- const stored = Math.max(usage.costUsdMicros ?? 0, 0);
212075
- if (stored > 0) return stored;
212076
- const estimated = estimateUsageCost(model, usage);
212077
- if (!estimated || estimated.micros <= 0) return 0;
212078
- if (estimated.currency === "USD") return estimated.micros;
212079
- return Math.round(estimated.micros / DISPLAY_CNY_PER_USD);
212080
- }
212081
- var FOREIGN_MODEL_MARKUP, usd, cny, MODEL_PRICES, STATIC_PREFIX_PRICES, CACHE_TTL_MS2, dbPrices, dbPrefixKeys, cacheLoadedAt, cacheLoader, cacheRefreshInFlight;
212082
- var init_model_pricing = __esm({
212083
- "../server/src/domains/actors/model-pricing.ts"() {
212084
- "use strict";
212085
- init_src();
212086
- FOREIGN_MODEL_MARKUP = 1.08;
212087
- usd = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
212088
- currency: "USD",
212089
- inputPerMillion: inputPerMillion * FOREIGN_MODEL_MARKUP,
212090
- outputPerMillion: outputPerMillion * FOREIGN_MODEL_MARKUP,
212091
- ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion: cachedInputPerMillion * FOREIGN_MODEL_MARKUP } : {}
212092
- });
212093
- cny = (inputPerMillion, outputPerMillion, cachedInputPerMillion) => ({
212094
- currency: "CNY",
212095
- inputPerMillion,
212096
- outputPerMillion,
212097
- ...cachedInputPerMillion !== void 0 ? { cachedInputPerMillion } : {}
212098
- });
212099
- MODEL_PRICES = {
212100
- "gpt-5.5": usd(5, 30, 0.5),
212101
- "gpt-5.5-pro": usd(30, 180),
212102
- "chat-latest": usd(5, 30, 0.5),
212103
- "gpt-5.4-mini": usd(0.75, 4.5, 0.075),
212104
- "gpt-5.4-nano": usd(0.2, 1.25, 0.02),
212105
- "gpt-5.4": usd(2.5, 15, 0.25),
212106
- "gpt-5.4-pro": usd(30, 180),
212107
- "gpt-5.3-codex": usd(1.75, 14, 0.175),
212108
- "gpt-5.2": usd(1.75, 14, 0.175),
212109
- "gpt-5.2-chat-latest": usd(1.75, 14, 0.175),
212110
- "gpt-5.2-pro": usd(21, 168),
212111
- "gpt-5.2-codex": usd(1.75, 14, 0.175),
212112
- "gpt-5.1": usd(1.25, 10, 0.125),
212113
- "gpt-5.1-chat-latest": usd(1.25, 10, 0.125),
212114
- "gpt-5.1-codex": usd(1.25, 10, 0.125),
212115
- "gpt-5.1-codex-max": usd(1.25, 10, 0.125),
212116
- "gpt-5.1-codex-mini": usd(0.25, 2, 0.025),
212117
- "gpt-5-pro": usd(15, 120),
212118
- "gpt-5-codex": usd(1.25, 10, 0.125),
212119
- "gpt-5-search-api": usd(1.25, 10, 0.125),
212120
- "gpt-5": usd(1.25, 10, 0.125),
212121
- "gpt-5-mini": usd(0.25, 2, 0.025),
212122
- "gpt-5-nano": usd(0.05, 0.4, 5e-3),
212123
- "gpt-5-chat-latest": usd(1.25, 10, 0.125),
212124
- "gpt-4.1": usd(2, 8, 0.5),
212125
- "gpt-4.1-2025-04-14": usd(2, 8, 0.5),
212126
- "gpt-4.1-mini": usd(0.4, 1.6, 0.1),
212127
- "gpt-4.1-mini-2025-04-14": usd(0.4, 1.6, 0.1),
212128
- "gpt-4.1-nano": usd(0.1, 0.4, 0.025),
212129
- "gpt-4.1-nano-2025-04-14": usd(0.1, 0.4, 0.025),
212130
- "o4-mini": usd(1.1, 4.4, 0.275),
212131
- // o3 / o3-mini 在 codex 静态目录里可选,却一直不在价目快照里(费用记 0)。
212132
- // 这两行按 OpenAI 公开价填,不是智增增快照里的行;供应商价目表同步到时以 DB 为准。
212133
- "o3": usd(2, 8),
212134
- "o3-mini": usd(1.1, 4.4),
212135
- "claude-fable-5-1": usd(10, 50),
212136
- "claude-fable-5": usd(10, 50),
212137
- "claude-mythos-5-1": usd(10, 50),
212138
- "claude-mythos-5": usd(10, 50),
212139
- // claude-opus-5 是 Claude Code 当代默认款,却一直不在这张快照里:查不到价 → 费用记 0
212140
- // (与 2026-09-04 那次 `claude-opus-5[1m]` 记 0 同一类坑)。按 Opus 档 $5/$25 补上。
212141
- "claude-opus-5": usd(5, 25),
212142
- "claude-opus-4-8": usd(5, 25),
212143
- "claude-opus-4-7": usd(5, 25),
212144
- "claude-sonnet-5": usd(2, 10),
212145
- "claude-sonnet-4-6": usd(3, 15),
212146
- "claude-opus-4-6": usd(5, 25),
212147
- "claude-opus-4-5-20251101": usd(5, 25),
212148
- "claude-opus-4-5": usd(5, 25),
212149
- "claude-sonnet-4-5-20250929": usd(3, 15),
212150
- // 短名同样要有:前缀比对是「表里的键是模型名的前缀」,带日期的那行匹配不上不带日期的短名。
212151
- // claude-sonnet-4-5 从 2026-07-04 起就在目录里可选,却一直查不到价(费用记 0),本次一并补。
212152
- "claude-sonnet-4-5": usd(3, 15),
212153
- "claude-opus-4-1-20250805": usd(15, 75),
212154
- "claude-opus-4-20250514": usd(15, 75),
212155
- "claude-sonnet-4-20250514": usd(3, 15),
212156
- "claude-3-7-sonnet-20250219": usd(3, 15),
212157
- "claude-3-5-sonnet-20241022": usd(3, 15),
212158
- "claude-3-5-sonnet-20240620": usd(3, 15),
212159
- "claude-3-sonnet-20240229": usd(3, 15),
212160
- "claude-haiku-4-5-20251001": usd(1, 5),
212161
- // codebuddy 目录用点号写法(claude-sonnet-4.6),与上面的连字符写法是同一批模型,
212162
- // 但字符串对不上就查不到价 → 记 0。同价另起别名行。
212163
- "claude-sonnet-4.6": usd(3, 15),
212164
- "claude-opus-4.7": usd(5, 25),
212165
- "gemini-3.1-pro": usd(2, 12),
212166
- "gemini-3-pro-preview": usd(2, 12),
212167
- // Claude Code 回报的可能是不带日期的短名,短名匹配不上带日期的那行(前缀比对是「表里的键是模型名的前缀」)。
212168
- "claude-haiku-4-5": usd(1, 5),
212169
- "gemini-3.5-flash": usd(1.5, 9),
212170
- "gemini-3.1-pro-preview": usd(2, 12),
212171
- "gemini-3.1-pro-preview-customtools": usd(2, 12),
212172
- "gemini-3.1-flash-lite-preview": usd(0.25, 1.5),
212173
- "gemini-3-flash-preview": usd(0.5, 3),
212174
- "gemini-2.5-pro": usd(1.25, 10),
212175
- "gemini-2.5-flash": usd(0.3, 2.5),
212176
- "gemini-2.5-flash-lite": usd(0.1, 0.4),
212177
- "gemini-2.5-flash-preview-09-2025": usd(0.3, 2.5),
212178
- "gemini-2.0-flash": usd(0.1, 0.4),
212179
- "deepseek-v4-flash": cny(1, 2, 0.02),
212180
- "deepseek-v4-pro": cny(3, 6, 0.025),
212181
- "deepseek-chat": cny(2, 3),
212182
- "deepseek-reasoner": cny(2, 3),
212183
- "deepseek-v3.2": cny(2, 3),
212184
- "deepseek-coder": cny(2, 3),
212185
- "deepseek-r1": cny(4, 16),
212186
- "deepseek-r1-0528": cny(4, 16),
212187
- "deepseek-v3": cny(2, 8),
212188
- "deepseek-v3-0324": cny(2, 8),
212189
- "deepseek-v3.1": cny(4, 12),
212190
- "qwen3-max": cny(6, 24),
212191
- "qwen3-max-preview": cny(6, 24),
212192
- "qwen3-coder-plus": cny(4, 16),
212193
- "qwen3-235b-a22b": cny(4, 40),
212194
- "qwen3-32b": cny(2, 20),
212195
- "qwen3-30b-a3b": cny(1.5, 15),
212196
- "qwen3-14b": cny(1, 10),
212197
- "qwen3-8b": cny(0.5, 5),
212198
- "qwen3-4b": cny(0.3, 3),
212199
- "qwen3-1.7b": cny(0.3, 3),
212200
- "qwen3-0.6b": cny(0.3, 3),
212201
- "qwq-plus": cny(1.6, 4),
212202
- "qwq-plus-latest": cny(1.6, 4),
212203
- "qwq-plus-2025-03-05": cny(1.6, 4),
212204
- "kimi-k2.7-code": cny(6.5, 27),
212205
- "kimi-k2.6": cny(6.5, 27),
212206
- "kimi-k2.5": cny(4, 21),
212207
- "kimi-k2-0905-preview": cny(4, 16),
212208
- "kimi-k2-0711-preview": cny(4, 16),
212209
- "kimi-k2-turbo-preview": cny(8, 58)
212210
- };
212211
- STATIC_PREFIX_PRICES = Object.keys(MODEL_PRICES).sort((a, b2) => b2.length - a.length);
212212
- CACHE_TTL_MS2 = 5 * 6e4;
212213
- dbPrices = null;
212214
- dbPrefixKeys = [];
212215
- cacheLoadedAt = 0;
212216
- cacheLoader = null;
212217
- cacheRefreshInFlight = null;
212218
- }
212219
- });
212220
-
212221
- // ../server/src/domains/usage/accounting.ts
212222
- function nonNegative(v2) {
212223
- return Math.max(v2 ?? 0, 0);
212224
- }
212225
- function resolvedModel(model) {
212226
- const trimmed = model?.trim();
212227
- return trimmed ? trimmed : null;
212228
- }
212229
- function splitByWeight(total, w2) {
212230
- const sum = w2.input + w2.cache + w2.output;
212231
- if (total === 0) return { input: 0, cache: 0, output: 0 };
212232
- if (sum <= 0) return { input: 0, cache: 0, output: total };
212233
- const input = Math.round(total * w2.input / sum);
212234
- const cache = Math.round(total * w2.cache / sum);
212235
- return { input, cache, output: total - input - cache };
212236
- }
212237
- function toDisplayUsd(currency, micros) {
212238
- return currency === "USD" ? micros : Math.round(micros / DISPLAY_CNY_PER_USD);
212239
- }
212240
- function accountUsage(model, usage) {
212241
- const resolved = resolvedModel(model);
212242
- if (!resolved || !usage) return null;
212243
- const input = nonNegative(usage.inputTokens);
212244
- const output = nonNegative(usage.outputTokens);
212245
- const cache = nonNegative(usage.cacheReadTokens) + nonNegative(usage.cacheCreationTokens);
212246
- const tokens = { input, cache, output, total: input + cache + output };
212247
- const estimated = estimateUsageCostBreakdown(resolved, usage);
212248
- const weights = estimated ? { input: estimated.inputMicros, cache: estimated.cacheMicros, output: estimated.outputMicros } : null;
212249
- const reported = Math.max(usage.costUsdMicros ?? 0, 0);
212250
- if (reported > 0) {
212251
- const parts = weights && estimated?.currency === "USD" ? splitByWeight(reported, weights) : { input: 0, cache: 0, output: reported };
212252
- return {
212253
- tokens,
212254
- currency: "USD",
212255
- amountMicros: reported,
212256
- parts,
212257
- displayUsdMicros: reported,
212258
- displayParts: parts,
212259
- source: "reported"
212260
- };
212261
- }
212262
- if (estimated && weights) {
212263
- const amount = weights.input + weights.cache + weights.output;
212264
- const displayUsdMicros = toDisplayUsd(estimated.currency, amount);
212265
- return {
212266
- tokens,
212267
- currency: estimated.currency,
212268
- amountMicros: amount,
212269
- parts: weights,
212270
- displayUsdMicros,
212271
- displayParts: estimated.currency === "USD" ? weights : splitByWeight(displayUsdMicros, weights),
212272
- source: "estimated"
212273
- };
212274
- }
212275
- return {
212276
- tokens,
212277
- currency: "USD",
212278
- amountMicros: 0,
212279
- parts: { input: 0, cache: 0, output: 0 },
212280
- displayUsdMicros: 0,
212281
- displayParts: { input: 0, cache: 0, output: 0 },
212282
- source: "unpriced"
212283
- };
212284
- }
212285
- function isUnpriced(account) {
212286
- return account.source === "unpriced" && account.tokens.total > 0;
212287
- }
212288
- var init_accounting = __esm({
212289
- "../server/src/domains/usage/accounting.ts"() {
212290
- "use strict";
212291
- init_model_pricing();
212292
- }
212293
- });
212294
-
212295
212549
  // ../server/src/domains/actors/stats.ts
212296
212550
  function emptyCostBreakdown() {
212297
212551
  return {
@@ -274183,7 +274437,7 @@ function shimScript() {
274183
274437
  }
274184
274438
 
274185
274439
  // src/index.ts
274186
- var PKG_VERSION = true ? "2.2.31" : "dev";
274440
+ var PKG_VERSION = true ? "2.2.32" : "dev";
274187
274441
  var LOCAL_BIN = localBin();
274188
274442
  var NPM_PREFIX = npmPrefix();
274189
274443
  var INSTANCE = DEFAULT_INSTANCE;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oasis_test_v2",
3
- "version": "2.2.31",
3
+ "version": "2.2.32",
4
4
  "description": "Oasis node daemon + CLI — background daemon, auto-start, full server CLI",
5
5
  "bin": {
6
6
  "oasis": "./dist/index.js"
@@ -26,6 +26,6 @@
26
26
  "node": ">=20"
27
27
  },
28
28
  "oasisRelease": {
29
- "sourceHead": "bbc56bf65b7e0c10506c3919f42c9c7d8b7c7342"
29
+ "sourceHead": "7e66896a0213e95be3142f0ac6972054b10ea1e3"
30
30
  }
31
31
  }