kimiflare 0.67.0 → 0.68.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/index.js CHANGED
@@ -6322,6 +6322,12 @@ var init_renderer = __esm({
6322
6322
  });
6323
6323
 
6324
6324
  // src/cost-attribution/reconcile.ts
6325
+ var reconcile_exports = {};
6326
+ __export(reconcile_exports, {
6327
+ aggregateByFeature: () => aggregateByFeature,
6328
+ fetchGatewayLogs: () => fetchGatewayLogs,
6329
+ reconcileWithCloudflare: () => reconcileWithCloudflare
6330
+ });
6325
6331
  function cacheKey(opts2) {
6326
6332
  return `${opts2.gatewayId ?? "none"}:${opts2.startDate}:${opts2.endDate}`;
6327
6333
  }
@@ -6360,6 +6366,28 @@ async function fetchGatewayLogs(accountId, apiToken, gatewayId, startDate, endDa
6360
6366
  }
6361
6367
  return out;
6362
6368
  }
6369
+ function aggregateByFeature(logs) {
6370
+ const map = /* @__PURE__ */ new Map();
6371
+ for (const log2 of logs) {
6372
+ let feature = "unknown";
6373
+ const m = log2.metadata;
6374
+ if (m && typeof m === "object" && !Array.isArray(m)) {
6375
+ const f = m.feature;
6376
+ if (typeof f === "string") feature = f;
6377
+ } else if (typeof m === "string") {
6378
+ try {
6379
+ const parsed = JSON.parse(m);
6380
+ if (typeof parsed.feature === "string") feature = parsed.feature;
6381
+ } catch {
6382
+ }
6383
+ }
6384
+ const entry = map.get(feature) ?? { feature, cost: 0, requests: 0 };
6385
+ entry.cost += typeof log2.cost === "number" ? log2.cost : 0;
6386
+ entry.requests += 1;
6387
+ map.set(feature, entry);
6388
+ }
6389
+ return Array.from(map.values()).sort((a, b) => b.cost - a.cost);
6390
+ }
6363
6391
  async function reconcileWithCloudflare(opts2) {
6364
6392
  if (!opts2.accountId || !opts2.apiToken) {
6365
6393
  return {
@@ -6399,7 +6427,8 @@ async function reconcileWithCloudflare(opts2) {
6399
6427
  localCost: opts2.localCost,
6400
6428
  cloudflareCost,
6401
6429
  driftPct: Math.round(driftPct * 1e3) / 10,
6402
- message: `Reconciled ${logs.length} Gateway log entries`
6430
+ message: `Reconciled ${logs.length} Gateway log entries`,
6431
+ featureBreakdown: aggregateByFeature(logs)
6403
6432
  };
6404
6433
  cache.set(key, { result, expires: Date.now() + 60 * 60 * 1e3 });
6405
6434
  return result;
@@ -9459,7 +9488,8 @@ async function getCostReport(sessionId) {
9459
9488
  gatewayRequests: rawSession.gatewayRequests,
9460
9489
  gatewayCachedRequests: rawSession.gatewayCachedRequests,
9461
9490
  gatewayCost: rawSession.gatewayCost,
9462
- reconcilePending: hasPendingReconcile(rawSession)
9491
+ reconcilePending: hasPendingReconcile(rawSession),
9492
+ lastTurnMs: latestConfirmedDurationMs(rawSession)
9463
9493
  } : { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
9464
9494
  const todayUsage = log2.days.find((d) => d.date === date) ?? { date, promptTokens: 0, completionTokens: 0, cachedTokens: 0, cost: 0 };
9465
9495
  const monthUsage = {
@@ -9504,6 +9534,14 @@ function hasPendingReconcile(session) {
9504
9534
  (t) => t.logId && t.confirmedCost === void 0 && !t.reconcileFailed
9505
9535
  );
9506
9536
  }
9537
+ function latestConfirmedDurationMs(session) {
9538
+ if (!session.turns) return void 0;
9539
+ for (let i = session.turns.length - 1; i >= 0; i--) {
9540
+ const ms = session.turns[i]?.durationMs;
9541
+ if (typeof ms === "number") return ms;
9542
+ }
9543
+ return void 0;
9544
+ }
9507
9545
  async function getSessionGatewayLogs(sessionId) {
9508
9546
  const log2 = await loadLog2();
9509
9547
  const session = log2.sessions.find((s) => s.id === sessionId);
@@ -9549,6 +9587,17 @@ function formatGatewaySection(report, accountId, gatewayId, recentLogs2 = []) {
9549
9587
  );
9550
9588
  return lines.join("\n");
9551
9589
  }
9590
+ function formatFeatureBreakdown(breakdown) {
9591
+ if (!breakdown || breakdown.length === 0) return "";
9592
+ if (breakdown.length === 1 && breakdown[0].feature === "unknown") return "";
9593
+ const lines = ["\u2500\u2500\u2500 By feature (Gateway-confirmed) \u2500\u2500\u2500"];
9594
+ for (const row of breakdown) {
9595
+ lines.push(
9596
+ ` ${row.feature.padEnd(20)} $${row.cost.toFixed(4)} (${row.requests} req)`
9597
+ );
9598
+ }
9599
+ return lines.join("\n");
9600
+ }
9552
9601
  function formatCostReport(report) {
9553
9602
  const lines = [];
9554
9603
  const add = (label, u) => {
@@ -11752,6 +11801,9 @@ function buildRightParts(usage, contextLimit, sessionUsage, gatewayMeta, cloudMo
11752
11801
  } else {
11753
11802
  parts.push(`${prefix}${sessionUsage.cost.toFixed(2)}`);
11754
11803
  }
11804
+ if (typeof sessionUsage.lastTurnMs === "number") {
11805
+ parts.push(formatDuration(sessionUsage.lastTurnMs));
11806
+ }
11755
11807
  } else {
11756
11808
  const cached = usage.prompt_tokens_details?.cached_tokens ?? 0;
11757
11809
  const cost = calculateCost(usage.prompt_tokens, usage.completion_tokens, cached);
@@ -11780,6 +11832,10 @@ function formatGatewayCacheStatus(gatewayMeta) {
11780
11832
  const status = gatewayMeta?.cacheStatus?.trim();
11781
11833
  return status ? `AI Gateway \xB7 cache ${status.toLowerCase()}` : null;
11782
11834
  }
11835
+ function formatDuration(ms) {
11836
+ if (ms < 1e3) return `${Math.round(ms)}ms`;
11837
+ return `${(ms / 1e3).toFixed(1)}s`;
11838
+ }
11783
11839
  function formatElapsed2(ms) {
11784
11840
  const total = Math.floor(ms / 1e3);
11785
11841
  const m = Math.floor(total / 60);
@@ -20079,6 +20135,22 @@ ${wcagWarnings.join("\n")}` }
20079
20135
  const logs = sid ? await getSessionGatewayLogs(sid).catch(() => []) : [];
20080
20136
  const gwSection = formatGatewaySection(report, cfg.accountId, cfg.aiGatewayId, logs);
20081
20137
  if (gwSection) lines.push("", gwSection);
20138
+ try {
20139
+ const { reconcileWithCloudflare: reconcileWithCloudflare2 } = await Promise.resolve().then(() => (init_reconcile(), reconcile_exports));
20140
+ const today4 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
20141
+ const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1e3).toISOString().slice(0, 10);
20142
+ const recon = await reconcileWithCloudflare2({
20143
+ localCost: report.month.cost,
20144
+ accountId: cfg.accountId,
20145
+ apiToken: cfg.apiToken,
20146
+ gatewayId: cfg.aiGatewayId,
20147
+ startDate: sevenDaysAgo,
20148
+ endDate: today4
20149
+ });
20150
+ const breakdown = formatFeatureBreakdown(recon.featureBreakdown);
20151
+ if (breakdown) lines.push("", breakdown);
20152
+ } catch {
20153
+ }
20082
20154
  }
20083
20155
  if (cfg?.costAttribution) {
20084
20156
  const { getCategoryReportText: getCategoryReportText2 } = await Promise.resolve().then(() => (init_tui_report(), tui_report_exports));
@@ -20156,6 +20228,24 @@ ${wcagWarnings.join("\n")}` }
20156
20228
  lines.push(`collect-logs: ${cfg.aiGatewayCollectLogPayload ?? false}`);
20157
20229
  const meta = cfg.aiGatewayMetadata;
20158
20230
  lines.push(`metadata: ${meta && Object.keys(meta).length > 0 ? JSON.stringify(meta) : "none"}`);
20231
+ const sid = sessionIdRef.current;
20232
+ if (sid) {
20233
+ void getCostReport(sid).then((report) => {
20234
+ const req = report.session.gatewayRequests ?? 0;
20235
+ if (req === 0) return;
20236
+ const cached = report.session.gatewayCachedRequests ?? 0;
20237
+ const pct = (cached / req * 100).toFixed(1);
20238
+ setEvents((e) => [
20239
+ ...e,
20240
+ {
20241
+ kind: "info",
20242
+ key: mkKey(),
20243
+ text: `cache hits (session): ${cached}/${req} (${pct}%)`
20244
+ }
20245
+ ]);
20246
+ }).catch(() => {
20247
+ });
20248
+ }
20159
20249
  } else {
20160
20250
  lines.push("gateway: off (direct Workers AI)");
20161
20251
  }