anygate 0.5.3 → 0.5.5

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.
@@ -10,7 +10,7 @@ import { join } from "path";
10
10
  // package.json
11
11
  var package_default = {
12
12
  name: "anygate",
13
- version: "0.5.3",
13
+ version: "0.5.5",
14
14
  publishConfig: {
15
15
  access: "public"
16
16
  },
@@ -47,13 +47,15 @@ var package_default = {
47
47
  node: ">=18"
48
48
  },
49
49
  scripts: {
50
- build: "tsup && node scripts/copy-ui-assets.mjs",
50
+ build: "tsup && npm run ui:build && node scripts/copy-ui-assets.mjs",
51
51
  dev: "tsup --watch",
52
52
  test: "vitest run",
53
53
  "test:watch": "vitest",
54
54
  typecheck: "tsc --noEmit",
55
55
  "refresh:models-dev": "node scripts/refresh-models-dev-cache.mjs",
56
- prepublishOnly: `node -e "if (require('./package.json').version !== require('./package-lock.json').version) { console.error('Error: package.json and package-lock.json versions are out of sync! Run npm install to sync.'); process.exit(1); }" && npm run build`
56
+ prepublishOnly: `node -e "if (require('./package.json').version !== require('./package-lock.json').version) { console.error('Error: package.json and package-lock.json versions are out of sync! Run npm install to sync.'); process.exit(1); }" && npm run build`,
57
+ "ui:dev": "npm --prefix ui run dev",
58
+ "ui:build": "npm --prefix ui run build"
57
59
  },
58
60
  dependencies: {
59
61
  "@ai-sdk/alibaba": "^1.0.26",
@@ -3903,7 +3905,7 @@ function printTraceLog(debugLogPath) {
3903
3905
 
3904
3906
  // src/gateway/anthropic-proxy.ts
3905
3907
  import { createServer } from "http";
3906
- import { appendFileSync as appendFileSync2, openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2 } from "fs";
3908
+ import { appendFileSync as appendFileSync3, openSync as openSync3, writeSync as writeSync3, closeSync as closeSync3 } from "fs";
3907
3909
 
3908
3910
  // src/core/http-utils.ts
3909
3911
  import * as zlib from "zlib";
@@ -4780,8 +4782,8 @@ function getUsage(chunk) {
4780
4782
  const u = resp?.usageMetadata;
4781
4783
  if (!u) return null;
4782
4784
  return {
4783
- input: u.promptTokenCount ?? 0,
4784
- output: u.candidatesTokenCount ?? 0
4785
+ inputTokens: u.promptTokenCount ?? 0,
4786
+ outputTokens: u.candidatesTokenCount ?? 0
4785
4787
  };
4786
4788
  }
4787
4789
  function partThoughtSignature(part) {
@@ -4840,7 +4842,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
4840
4842
  textBlockOpen: false,
4841
4843
  pendingThoughtSignature: void 0,
4842
4844
  toolCalls: [],
4843
- usage: { input: 0, output: 0 },
4845
+ usage: { inputTokens: 0, outputTokens: 0 },
4844
4846
  emittedTextChars: 0,
4845
4847
  suppressedThoughtChars: 0
4846
4848
  };
@@ -4867,7 +4869,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
4867
4869
  writeEvent(res, "message_delta", { type: "message_delta", delta: { stop_reason: "end_turn", stop_sequence: null }, usage: { output_tokens: 0 } });
4868
4870
  writeEvent(res, "message_stop", { type: "message_stop" });
4869
4871
  res.end();
4870
- return;
4872
+ return state.usage;
4871
4873
  }
4872
4874
  const reader = upstreamRes.body.getReader();
4873
4875
  const decoder = new TextDecoder();
@@ -4917,7 +4919,7 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
4917
4919
  finalStopReason = mapStopReason(finishReason);
4918
4920
  log7?.(() => {
4919
4921
  const toolNames = state.toolCalls.map((tc) => tc.name).filter(Boolean).join(",");
4920
- return `cloud-code stream finish=${finishReason} mapped=${finalStopReason} ${summarizeParts(parts)} emittedTextChars=${state.emittedTextChars} suppressedThoughtChars=${state.suppressedThoughtChars} queuedToolCalls=${state.toolCalls.length} queuedToolNames=${toolNames || "-"} outputTokens=${state.usage.output}`;
4922
+ return `cloud-code stream finish=${finishReason} mapped=${finalStopReason} ${summarizeParts(parts)} emittedTextChars=${state.emittedTextChars} suppressedThoughtChars=${state.suppressedThoughtChars} queuedToolCalls=${state.toolCalls.length} queuedToolNames=${toolNames || "-"} outputTokens=${state.usage.outputTokens}`;
4921
4923
  });
4922
4924
  if (state.textBlockOpen) {
4923
4925
  closeBlock(res, state);
@@ -4949,11 +4951,11 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
4949
4951
  writeEvent(res, "message_delta", {
4950
4952
  type: "message_delta",
4951
4953
  delta: { stop_reason: anthropicStopReason, stop_sequence: null },
4952
- usage: { output_tokens: state.usage.output }
4954
+ usage: { output_tokens: state.usage.outputTokens }
4953
4955
  });
4954
4956
  writeEvent(res, "message_stop", { type: "message_stop" });
4955
4957
  res.end();
4956
- return;
4958
+ return state.usage;
4957
4959
  }
4958
4960
  }
4959
4961
  }
@@ -4967,10 +4969,11 @@ async function streamCloudCodeToAnthropic(res, upstreamRes, model, log7) {
4967
4969
  writeEvent(res, "message_delta", {
4968
4970
  type: "message_delta",
4969
4971
  delta: { stop_reason: finalStopReason, stop_sequence: null },
4970
- usage: { output_tokens: state.usage.output }
4972
+ usage: { output_tokens: state.usage.outputTokens }
4971
4973
  });
4972
4974
  writeEvent(res, "message_stop", { type: "message_stop" });
4973
4975
  res.end();
4976
+ return state.usage;
4974
4977
  }
4975
4978
  async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
4976
4979
  const text4 = await upstreamRes.text();
@@ -4986,8 +4989,8 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
4986
4989
  if (!chunk) continue;
4987
4990
  const usage = getUsage(chunk);
4988
4991
  if (usage) {
4989
- inputTokens = usage.input;
4990
- outputTokens = usage.output;
4992
+ inputTokens = usage.inputTokens;
4993
+ outputTokens = usage.outputTokens;
4991
4994
  }
4992
4995
  const candidate = getCandidate(chunk);
4993
4996
  if (!candidate) continue;
@@ -5031,7 +5034,9 @@ async function collectCloudCodeToAnthropic(upstreamRes, model, log7) {
5031
5034
  model,
5032
5035
  stop_reason: stopReason,
5033
5036
  stop_sequence: null,
5034
- usage: { input_tokens: inputTokens, output_tokens: outputTokens }
5037
+ usage: { input_tokens: inputTokens, output_tokens: outputTokens },
5038
+ inputTokens,
5039
+ outputTokens
5035
5040
  };
5036
5041
  }
5037
5042
 
@@ -5403,7 +5408,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5403
5408
  log7?.(() => `sdk stream error (${errorType}): ${errMsg}`);
5404
5409
  closeOpen();
5405
5410
  emit("error", { type: "error", error: { type: errorType, message: errMsg } });
5406
- return;
5411
+ return { inputTokens: 0, outputTokens: 0 };
5407
5412
  }
5408
5413
  default:
5409
5414
  break;
@@ -5413,6 +5418,7 @@ async function writeAnthropicStream(fullStream, modelId, write, log7) {
5413
5418
  ensureStart();
5414
5419
  emit("message_delta", { type: "message_delta", delta: { stop_reason: finishReason, stop_sequence: null }, usage });
5415
5420
  emit("message_stop", { type: "message_stop" });
5421
+ return { inputTokens: usage.input_tokens, outputTokens: usage.output_tokens };
5416
5422
  }
5417
5423
  async function streamAnthropicResponse(model, params, modelId, write, log7) {
5418
5424
  const result = streamText({ model, ...params, onError: () => {
@@ -5427,7 +5433,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log7) {
5427
5433
  });
5428
5434
  Promise.resolve(result.usage).catch(() => {
5429
5435
  });
5430
- await writeAnthropicStream(result.fullStream, modelId, write, log7);
5436
+ return await writeAnthropicStream(result.fullStream, modelId, write, log7);
5431
5437
  }
5432
5438
  async function generateAnthropicResponse(model, params, modelId, options) {
5433
5439
  let text4;
@@ -5459,7 +5465,207 @@ async function generateAnthropicResponse(model, params, modelId, options) {
5459
5465
  }))
5460
5466
  ],
5461
5467
  stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
5462
- usage: { input_tokens: usage?.inputTokens ?? 0, output_tokens: usage?.outputTokens ?? 0 }
5468
+ usage: { input_tokens: usage?.inputTokens ?? 0, output_tokens: usage?.outputTokens ?? 0 },
5469
+ // Internal: surfaced to call sites so they can log analytics without re-parsing.
5470
+ _usage: { inputTokens: usage?.inputTokens ?? 0, outputTokens: usage?.outputTokens ?? 0 }
5471
+ };
5472
+ }
5473
+
5474
+ // src/core/analytics-log.ts
5475
+ import { appendFileSync as appendFileSync2, openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2, readFileSync as readFileSync9, existsSync as existsSync9 } from "fs";
5476
+ import { join as join9 } from "path";
5477
+ var ANALYTICS_FILE = "analytics.jsonl";
5478
+ function normalizeModelKey(modelId) {
5479
+ return modelId.toLowerCase().replace(/\//g, ":").replace(/\s*\([^)]*\)\s*$/g, "").replace(/\s+/g, " ").trim();
5480
+ }
5481
+ function analyticsPath() {
5482
+ return join9(getAppHome(), ANALYTICS_FILE);
5483
+ }
5484
+ function appendAtomic(path, line) {
5485
+ try {
5486
+ const fd = openSync2(path, "a", 384);
5487
+ try {
5488
+ writeSync2(fd, line + "\n");
5489
+ } finally {
5490
+ closeSync2(fd);
5491
+ }
5492
+ } catch {
5493
+ try {
5494
+ appendFileSync2(path, line + "\n");
5495
+ } catch {
5496
+ }
5497
+ }
5498
+ }
5499
+ function recordUsage(event) {
5500
+ if (!event?.ts || typeof event.modelId !== "string" || typeof event.app !== "string") return;
5501
+ const inputTokens = Math.max(0, Math.floor(event.inputTokens || 0));
5502
+ const outputTokens = Math.max(0, Math.floor(event.outputTokens || 0));
5503
+ if (inputTokens === 0 && outputTokens === 0) return;
5504
+ const clean = {
5505
+ ts: event.ts,
5506
+ modelId: event.modelId,
5507
+ app: event.app,
5508
+ inputTokens,
5509
+ outputTokens
5510
+ };
5511
+ if (event.npm) clean.npm = event.npm;
5512
+ if (event.providerId) clean.providerId = event.providerId;
5513
+ appendAtomic(analyticsPath(), JSON.stringify(clean));
5514
+ }
5515
+ function readAnalyticsLog() {
5516
+ const path = analyticsPath();
5517
+ if (!existsSync9(path)) return [];
5518
+ let raw;
5519
+ try {
5520
+ raw = readFileSync9(path, "utf8");
5521
+ } catch {
5522
+ return [];
5523
+ }
5524
+ const out = [];
5525
+ for (const line of raw.split("\n")) {
5526
+ const t = line.trim();
5527
+ if (!t) continue;
5528
+ try {
5529
+ const e = JSON.parse(t);
5530
+ if (e && typeof e.ts === "string" && typeof e.modelId === "string") out.push(e);
5531
+ } catch {
5532
+ }
5533
+ }
5534
+ return out;
5535
+ }
5536
+ var MODEL_PALETTE = [
5537
+ "oklch(75% 0.16 65)",
5538
+ // amber (accent)
5539
+ "oklch(70% 0.15 200)",
5540
+ // sky blue
5541
+ "oklch(68% 0.16 150)",
5542
+ // teal/green
5543
+ "oklch(72% 0.17 300)",
5544
+ // violet
5545
+ "oklch(70% 0.18 20)",
5546
+ // rose/red
5547
+ "oklch(74% 0.15 95)"
5548
+ // gold/yellow
5549
+ ];
5550
+ function rangeDays(range) {
5551
+ if (range === "7d") return 7;
5552
+ if (range === "30d") return 30;
5553
+ return 365;
5554
+ }
5555
+ function dayKey(iso) {
5556
+ return iso.slice(0, 10);
5557
+ }
5558
+ function aggregateAnalytics(range) {
5559
+ const all = readAnalyticsLog();
5560
+ const today = /* @__PURE__ */ new Date();
5561
+ today.setUTCHours(0, 0, 0, 0);
5562
+ const cutoff = new Date(today);
5563
+ cutoff.setUTCDate(today.getUTCDate() - (rangeDays(range) - 1));
5564
+ const cutoffIso = cutoff.toISOString();
5565
+ const endIso = new Date(today.getTime() + 864e5).toISOString();
5566
+ const events = all.filter((e) => e.ts >= cutoffIso && e.ts <= endIso);
5567
+ const eventsByDay = /* @__PURE__ */ new Map();
5568
+ const tokensByDay = /* @__PURE__ */ new Map();
5569
+ const hourCounts = new Array(24).fill(0);
5570
+ const activeDaySet = /* @__PURE__ */ new Set();
5571
+ const modelMap = /* @__PURE__ */ new Map();
5572
+ let totalTokens = 0;
5573
+ let messages = 0;
5574
+ for (const e of events) {
5575
+ const day = dayKey(e.ts);
5576
+ const tok = e.inputTokens + e.outputTokens;
5577
+ totalTokens += tok;
5578
+ messages += 1;
5579
+ eventsByDay.set(day, (eventsByDay.get(day) ?? 0) + 1);
5580
+ tokensByDay.set(day, (tokensByDay.get(day) ?? 0) + tok);
5581
+ activeDaySet.add(day);
5582
+ const hour = Number(e.ts.slice(11, 13));
5583
+ if (Number.isFinite(hour) && hour >= 0 && hour < 24) hourCounts[hour] += 1;
5584
+ const provider = e.providerId ?? e.npm?.replace(/^@/, "").replace(/\//g, "-") ?? "unknown";
5585
+ const key = `${provider}|${normalizeModelKey(e.modelId)}`;
5586
+ const m = modelMap.get(key) ?? { provider, model: e.modelId, app: e.app, apps: /* @__PURE__ */ new Set(), inputTokens: 0, outputTokens: 0 };
5587
+ if (!/\s/.test(m.model) && /\s/.test(e.modelId)) m.model = e.modelId;
5588
+ m.inputTokens += e.inputTokens;
5589
+ m.outputTokens += e.outputTokens;
5590
+ m.apps.add(e.app);
5591
+ modelMap.set(key, m);
5592
+ }
5593
+ const busiestDay = Math.max(1, ...[...tokensByDay.values()]);
5594
+ const heatmap = [];
5595
+ for (let i = rangeDays(range) - 1; i >= 0; i--) {
5596
+ const d = new Date(today);
5597
+ d.setUTCDate(today.getUTCDate() - i);
5598
+ const date = d.toISOString().slice(0, 10);
5599
+ const tokens = tokensByDay.get(date) ?? 0;
5600
+ let intensity = 0;
5601
+ if (tokens > 0) {
5602
+ const pct = tokens / busiestDay;
5603
+ intensity = Math.max(1, Math.min(4, Math.round(1 + pct * 3)));
5604
+ }
5605
+ heatmap.push({ date, count: tokens, intensity });
5606
+ }
5607
+ const dailyTokens = [];
5608
+ for (let i = rangeDays(range) - 1; i >= 0; i--) {
5609
+ const d = new Date(today);
5610
+ d.setUTCDate(today.getUTCDate() - i);
5611
+ const date = d.toISOString().slice(0, 10);
5612
+ dailyTokens.push({ date, tokens: tokensByDay.get(date) ?? 0 });
5613
+ }
5614
+ let currentStreak = 0;
5615
+ for (let i = 0; i < rangeDays(range); i++) {
5616
+ const d = new Date(today);
5617
+ d.setUTCDate(today.getUTCDate() - i);
5618
+ const date = d.toISOString().slice(0, 10);
5619
+ if (eventsByDay.has(date)) currentStreak++;
5620
+ else break;
5621
+ }
5622
+ let longestStreak = 0;
5623
+ let run3 = 0;
5624
+ for (const day of heatmap) {
5625
+ if (day.count > 0) {
5626
+ run3++;
5627
+ longestStreak = Math.max(longestStreak, run3);
5628
+ } else run3 = 0;
5629
+ }
5630
+ let peakHour = 0;
5631
+ let peakCount = -1;
5632
+ for (let h = 0; h < 24; h++) {
5633
+ if (hourCounts[h] > peakCount) {
5634
+ peakCount = hourCounts[h];
5635
+ peakHour = h;
5636
+ }
5637
+ }
5638
+ const models = [...modelMap.entries()].map(([, m], idx) => {
5639
+ const share = totalTokens > 0 ? (m.inputTokens + m.outputTokens) / totalTokens : 0;
5640
+ const apps = [...m.apps];
5641
+ return {
5642
+ provider: m.provider,
5643
+ model: m.model,
5644
+ tier: "",
5645
+ // source tier isn't tracked in the log; UI shows it from catalog elsewhere
5646
+ app: apps[0] ?? m.app,
5647
+ apps,
5648
+ inputTokens: m.inputTokens,
5649
+ outputTokens: m.outputTokens,
5650
+ share,
5651
+ color: MODEL_PALETTE[idx % MODEL_PALETTE.length]
5652
+ };
5653
+ });
5654
+ models.sort((a, b) => b.share - a.share);
5655
+ const favoriteModel = models.length > 0 ? `${models[0].provider}: ${models[0].model}` : "";
5656
+ return {
5657
+ range,
5658
+ sessions: activeDaySet.size,
5659
+ messages,
5660
+ totalTokens,
5661
+ activeDays: activeDaySet.size,
5662
+ currentStreakDays: currentStreak,
5663
+ longestStreakDays: longestStreak,
5664
+ peakHour,
5665
+ favoriteModel,
5666
+ heatmap,
5667
+ dailyTokens,
5668
+ models
5463
5669
  };
5464
5670
  }
5465
5671
 
@@ -5467,16 +5673,16 @@ async function generateAnthropicResponse(model, params, modelId, options) {
5467
5673
  function appendSecureLog(logPath, line) {
5468
5674
  const redacted = redactTraceLine(line);
5469
5675
  try {
5470
- const fd = openSync2(logPath, "a", 384);
5676
+ const fd = openSync3(logPath, "a", 384);
5471
5677
  try {
5472
- writeSync2(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
5678
+ writeSync3(fd, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
5473
5679
  `);
5474
5680
  } finally {
5475
- closeSync2(fd);
5681
+ closeSync3(fd);
5476
5682
  }
5477
5683
  } catch {
5478
5684
  try {
5479
- appendFileSync2(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
5685
+ appendFileSync3(logPath, `${(/* @__PURE__ */ new Date()).toISOString()} ${redacted}
5480
5686
  `);
5481
5687
  } catch {
5482
5688
  }
@@ -5665,7 +5871,16 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
5665
5871
  "Cache-Control": "no-cache",
5666
5872
  "Connection": "keep-alive"
5667
5873
  });
5668
- await streamAnthropicResponse(model, params, originalModel, (c) => res.write(c), plog);
5874
+ const usage = await streamAnthropicResponse(model, params, originalModel, (c) => res.write(c), plog);
5875
+ recordUsage({
5876
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5877
+ modelId: route.realModelId,
5878
+ npm: route.npm,
5879
+ providerId: route.providerId,
5880
+ app: route.app ?? "gateway",
5881
+ inputTokens: usage.inputTokens,
5882
+ outputTokens: usage.outputTokens
5883
+ });
5669
5884
  res.end();
5670
5885
  } else {
5671
5886
  const anthropicResponse = await generateAnthropicResponse(
@@ -5674,12 +5889,22 @@ function startProxyCatalog(routes, defaultAliasId, debug = false) {
5674
5889
  originalModel,
5675
5890
  { forceStream: openAiOAuth }
5676
5891
  );
5892
+ const u = anthropicResponse._usage;
5893
+ recordUsage({
5894
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5895
+ modelId: route.realModelId,
5896
+ npm: route.npm,
5897
+ providerId: route.providerId,
5898
+ app: route.app ?? "gateway",
5899
+ inputTokens: u?.inputTokens ?? 0,
5900
+ outputTokens: u?.outputTokens ?? 0
5901
+ });
5677
5902
  sendJson(res, 200, anthropicResponse);
5678
5903
  }
5679
5904
  } catch (err) {
5680
5905
  const message = err instanceof Error ? err.message : String(err);
5681
5906
  const body = err && typeof err === "object" && "responseBody" in err ? err.responseBody : void 0;
5682
- plog(() => `sdk error: ${message}${body ? ` \u2014 body: ${body}` : ""}`);
5907
+ plog(() => `sdk error: ${message}${body ? ` \u0393\xC7\xF6 body: ${body}` : ""}`);
5683
5908
  if (!res.headersSent) {
5684
5909
  const status = upstreamHttpStatus(err);
5685
5910
  anthropicError(res, status === 500 ? 502 : status, message);
@@ -5697,7 +5922,7 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
5697
5922
  if (route.modelFormat === "cloud-code") {
5698
5923
  const projectId = route.providerData?.projectId ?? "";
5699
5924
  if (!projectId) {
5700
- anthropicError(res, 500, "Antigravity provider missing projectId \u2014 re-authenticate with anygate providers auth antigravity");
5925
+ anthropicError(res, 500, "Antigravity provider missing projectId \u0393\xC7\xF6 re-authenticate with anygate providers auth antigravity");
5701
5926
  return;
5702
5927
  }
5703
5928
  const envelope = anthropicToCloudCode(anthropicBody, route.realModelId, projectId);
@@ -5708,7 +5933,7 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
5708
5933
  const cloudMaxOutput = envelope.request.generationConfig?.maxOutputTokens;
5709
5934
  const baseUrl = upstreamUrl.replace(/\/+$/, "");
5710
5935
  const cloudCodeUrl = `${baseUrl}/v1internal:streamGenerateContent?alt=sse`;
5711
- plog(() => `cloud-code: model=${route.realModelId}, project=${projectId.slice(0, 8)}\u2026 msgs=${cloudContents.length} toolCalls=${cloudToolCalls} toolResults=${cloudToolResults} tools=${cloudTools} maxOutput=${cloudMaxOutput ?? "unset"} stream=${clientWantsStream}`);
5936
+ plog(() => `cloud-code: model=${route.realModelId}, project=${projectId.slice(0, 8)}\u0393\xC7\xAA msgs=${cloudContents.length} toolCalls=${cloudToolCalls} toolResults=${cloudToolResults} tools=${cloudTools} maxOutput=${cloudMaxOutput ?? "unset"} stream=${clientWantsStream}`);
5712
5937
  const fetchCloudCode = (token) => fetch(cloudCodeUrl, {
5713
5938
  method: "POST",
5714
5939
  headers: {
@@ -5728,12 +5953,22 @@ data: ${JSON.stringify({ type: "error", error: { type: errorType, message } })}
5728
5953
  anthropicError(res, upstream.status >= 500 ? 502 : upstream.status, errBody);
5729
5954
  return;
5730
5955
  }
5956
+ let usage = { inputTokens: 0, outputTokens: 0 };
5731
5957
  if (clientWantsStream) {
5732
- await streamCloudCodeToAnthropic(res, upstream, route.realModelId, plog);
5958
+ usage = await streamCloudCodeToAnthropic(res, upstream, route.realModelId, plog);
5733
5959
  } else {
5734
5960
  const response = await collectCloudCodeToAnthropic(upstream, route.realModelId, plog);
5961
+ usage = { inputTokens: response.inputTokens ?? 0, outputTokens: response.outputTokens ?? 0 };
5735
5962
  sendJson(res, 200, response);
5736
5963
  }
5964
+ recordUsage({
5965
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
5966
+ modelId: route.realModelId,
5967
+ providerId: route.providerId,
5968
+ app: route.app ?? "Antigravity",
5969
+ inputTokens: usage.inputTokens,
5970
+ outputTokens: usage.outputTokens
5971
+ });
5737
5972
  } catch (err) {
5738
5973
  const message = err instanceof Error ? err.message : String(err);
5739
5974
  plog(() => `cloud-code fetch error: ${message}`);
@@ -5789,7 +6024,8 @@ function startProxy(completionsUrl, modelId, debug = false, contextWindow, sdk,
5789
6024
  reasoning: sdk?.reasoning,
5790
6025
  interleavedReasoningField: sdk?.interleavedReasoningField,
5791
6026
  useResponsesLite: sdk?.useResponsesLite,
5792
- preferWebSockets: sdk?.preferWebSockets
6027
+ preferWebSockets: sdk?.preferWebSockets,
6028
+ app: sdk?.app
5793
6029
  }], clientModelId, debug);
5794
6030
  }
5795
6031
 
@@ -6959,17 +7195,24 @@ async function generateOpenAiResponse(model, params, responseModelId) {
6959
7195
  function: { name: tc.toolName, arguments: JSON.stringify(tc.args) }
6960
7196
  }));
6961
7197
  }
7198
+ const usage = {
7199
+ inputTokens: result.usage?.promptTokens ?? 0,
7200
+ outputTokens: result.usage?.completionTokens ?? 0
7201
+ };
6962
7202
  return {
6963
- id: `chatcmpl-${Date.now()}`,
6964
- object: "chat.completion",
6965
- created: Math.floor(Date.now() / 1e3),
6966
- model: responseModelId,
6967
- choices: [{ index: 0, message, finish_reason: result.finishReason || "stop" }],
6968
- usage: {
6969
- prompt_tokens: result.usage?.promptTokens ?? 0,
6970
- completion_tokens: result.usage?.completionTokens ?? 0,
6971
- total_tokens: result.usage?.totalTokens ?? 0
6972
- }
7203
+ response: {
7204
+ id: `chatcmpl-${Date.now()}`,
7205
+ object: "chat.completion",
7206
+ created: Math.floor(Date.now() / 1e3),
7207
+ model: responseModelId,
7208
+ choices: [{ index: 0, message, finish_reason: result.finishReason || "stop" }],
7209
+ usage: {
7210
+ prompt_tokens: usage.inputTokens,
7211
+ completion_tokens: usage.outputTokens,
7212
+ total_tokens: usage.inputTokens + usage.outputTokens
7213
+ }
7214
+ },
7215
+ usage
6973
7216
  };
6974
7217
  }
6975
7218
  async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
@@ -6980,6 +7223,7 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
6980
7223
  created: Math.floor(Date.now() / 1e3),
6981
7224
  model: responseModelId
6982
7225
  };
7226
+ let usage = { inputTokens: 0, outputTokens: 0 };
6983
7227
  const send = (delta, finish_reason = null) => onChunk(`data: ${JSON.stringify({ ...baseData, choices: [{ index: 0, delta, finish_reason }] })}
6984
7228
 
6985
7229
  `);
@@ -6998,11 +7242,15 @@ async function streamOpenAiResponse(model, params, responseModelId, onChunk) {
6998
7242
  send({ tool_calls: [{ index: 0, function: { arguments: p8.delta ?? p8.text ?? p8.argsTextDelta ?? "" } }] });
6999
7243
  break;
7000
7244
  case "finish":
7245
+ if (p8.usage) {
7246
+ usage = { inputTokens: p8.usage.promptTokens ?? 0, outputTokens: p8.usage.completionTokens ?? 0 };
7247
+ }
7001
7248
  send({}, p8.finishReason || "stop");
7002
7249
  break;
7003
7250
  }
7004
7251
  }
7005
7252
  onChunk("data: [DONE]\n\n");
7253
+ return usage;
7006
7254
  }
7007
7255
 
7008
7256
  // src/registry/refresh-credentials.ts
@@ -7305,10 +7553,28 @@ async function handleAnthropicMessages(req, res, options, modelCache, plog) {
7305
7553
  "Cache-Control": "no-cache",
7306
7554
  "Connection": "keep-alive"
7307
7555
  });
7308
- await streamAnthropicResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
7556
+ const usage = await streamAnthropicResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
7557
+ recordUsage({
7558
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7559
+ modelId: responseModelId,
7560
+ npm: model.npm,
7561
+ providerId: model.providerId,
7562
+ app: "gateway",
7563
+ inputTokens: usage.inputTokens,
7564
+ outputTokens: usage.outputTokens
7565
+ });
7309
7566
  res.end();
7310
7567
  } else {
7311
7568
  const anthropicResponse = await generateAnthropicResponse(languageModel, params, responseModelId);
7569
+ recordUsage({
7570
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7571
+ modelId: responseModelId,
7572
+ npm: model.npm,
7573
+ providerId: model.providerId,
7574
+ app: "gateway",
7575
+ inputTokens: anthropicResponse._usage?.inputTokens ?? 0,
7576
+ outputTokens: anthropicResponse._usage?.outputTokens ?? 0
7577
+ });
7312
7578
  sendJson(res, 200, anthropicResponse);
7313
7579
  }
7314
7580
  } catch (err) {
@@ -7360,10 +7626,28 @@ async function handleOpenAIChatCompletions(req, res, options, modelCache, plog)
7360
7626
  "Cache-Control": "no-cache",
7361
7627
  "Connection": "keep-alive"
7362
7628
  });
7363
- await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
7629
+ const usage = await streamOpenAiResponse(languageModel, params, responseModelId, (chunk) => res.write(chunk));
7630
+ recordUsage({
7631
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7632
+ modelId: responseModelId,
7633
+ npm: model.npm ?? (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0),
7634
+ providerId: model.providerId,
7635
+ app: "gateway",
7636
+ inputTokens: usage.inputTokens,
7637
+ outputTokens: usage.outputTokens
7638
+ });
7364
7639
  res.end();
7365
7640
  } else {
7366
- const response = await generateOpenAiResponse(languageModel, params, responseModelId);
7641
+ const { response, usage } = await generateOpenAiResponse(languageModel, params, responseModelId);
7642
+ recordUsage({
7643
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
7644
+ modelId: responseModelId,
7645
+ npm: model.npm ?? (model.modelFormat === "anthropic" ? "@ai-sdk/anthropic" : void 0),
7646
+ providerId: model.providerId,
7647
+ app: "gateway",
7648
+ inputTokens: usage.inputTokens,
7649
+ outputTokens: usage.outputTokens
7650
+ });
7367
7651
  sendJson(res, 200, response);
7368
7652
  }
7369
7653
  } catch (err) {
@@ -7558,9 +7842,9 @@ async function selectServerProviders(available, initial) {
7558
7842
  }
7559
7843
 
7560
7844
  // src/gateway/vertex.ts
7561
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
7845
+ import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
7562
7846
  import { homedir as homedir6 } from "os";
7563
- import { join as join9 } from "path";
7847
+ import { join as join10 } from "path";
7564
7848
  var DEFAULT_VERTEX_MODELS = [
7565
7849
  { id: "claude-sonnet-4-6", display_name: "Claude Sonnet 4.6" },
7566
7850
  { id: "claude-opus-4-6", display_name: "Claude Opus 4.6" },
@@ -7584,18 +7868,18 @@ function resolveVertexLocation(env = process.env) {
7584
7868
  return location.trim() || "global";
7585
7869
  }
7586
7870
  function defaultAdcCredentialsPath(home = homedir6()) {
7587
- return join9(home, ".config", "gcloud", "application_default_credentials.json");
7871
+ return join10(home, ".config", "gcloud", "application_default_credentials.json");
7588
7872
  }
7589
7873
  function hasApplicationDefaultCredentials(home = homedir6(), adcPath = defaultAdcCredentialsPath(home), env = process.env) {
7590
7874
  const explicitPath = env["GOOGLE_APPLICATION_CREDENTIALS"]?.trim();
7591
- if (explicitPath && existsSync9(explicitPath)) return true;
7592
- return existsSync9(adcPath);
7875
+ if (explicitPath && existsSync10(explicitPath)) return true;
7876
+ return existsSync10(adcPath);
7593
7877
  }
7594
7878
  function loadVertexModelEntries(env = process.env) {
7595
7879
  const configPath = getVertexModelsPath(env);
7596
- if (!existsSync9(configPath)) return DEFAULT_VERTEX_MODELS;
7880
+ if (!existsSync10(configPath)) return DEFAULT_VERTEX_MODELS;
7597
7881
  try {
7598
- const parsed = JSON.parse(readFileSync9(configPath, "utf8"));
7882
+ const parsed = JSON.parse(readFileSync10(configPath, "utf8"));
7599
7883
  if (!Array.isArray(parsed) || parsed.length === 0) return DEFAULT_VERTEX_MODELS;
7600
7884
  const models = parsed.filter(
7601
7885
  (entry) => !!entry && typeof entry === "object" && typeof entry.id === "string" && entry.id.length > 0 && typeof entry.display_name === "string" && entry.display_name.length > 0
@@ -8108,12 +8392,12 @@ async function runServerCommand(options = {}) {
8108
8392
  import {
8109
8393
  chmodSync as chmodSync5,
8110
8394
  mkdirSync as mkdirSync6,
8111
- readFileSync as readFileSync10,
8395
+ readFileSync as readFileSync11,
8112
8396
  renameSync as renameSync2,
8113
8397
  unlinkSync as unlinkSync2,
8114
8398
  writeFileSync as writeFileSync5
8115
8399
  } from "fs";
8116
- import { join as join10 } from "path";
8400
+ import { join as join11 } from "path";
8117
8401
  var UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1e3;
8118
8402
  var UPDATE_CHECK_TIMEOUT_MS = 2e3;
8119
8403
  var UPDATE_COMMAND = "npm install -g anygate@latest";
@@ -8159,11 +8443,11 @@ function isNewerVersion(currentVersion, latestVersion) {
8159
8443
  return comparePrerelease(current.prerelease, latest.prerelease) > 0;
8160
8444
  }
8161
8445
  function cachePath() {
8162
- return join10(getAppHome(), "update-check.json");
8446
+ return join11(getAppHome(), "update-check.json");
8163
8447
  }
8164
8448
  function readFreshCache(now) {
8165
8449
  try {
8166
- const parsed = JSON.parse(readFileSync10(cachePath(), "utf8"));
8450
+ const parsed = JSON.parse(readFileSync11(cachePath(), "utf8"));
8167
8451
  if (typeof parsed.latestVersion !== "string" || !parseVersion(parsed.latestVersion)) return null;
8168
8452
  if (typeof parsed.checkedAt !== "number" || !Number.isFinite(parsed.checkedAt)) return null;
8169
8453
  const age = now - parsed.checkedAt;
@@ -8244,18 +8528,18 @@ function favoriteProviderDisplayName(provider) {
8244
8528
 
8245
8529
  // src/providers/opencode-serve.ts
8246
8530
  import { execSync as execSync2, spawn as spawn2 } from "child_process";
8247
- import { existsSync as existsSync10 } from "fs";
8531
+ import { existsSync as existsSync11 } from "fs";
8248
8532
  import { homedir as homedir7 } from "os";
8249
- import { join as join11 } from "path";
8533
+ import { join as join12 } from "path";
8250
8534
  var isWindows2 = process.platform === "win32";
8251
8535
  var OPENCODE_FALLBACK_PATHS = isWindows2 ? [
8252
- join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8253
- join11(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8254
- join11(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8536
+ join12(process.env["APPDATA"] ?? homedir7(), "npm", "opencode.cmd"),
8537
+ join12(process.env["APPDATA"] ?? homedir7(), "npm", "opencode"),
8538
+ join12(homedir7(), "AppData", "Roaming", "npm", "opencode.cmd")
8255
8539
  ] : [
8256
- join11(homedir7(), ".opencode", "bin", "opencode"),
8257
- join11(homedir7(), ".local", "bin", "opencode"),
8258
- join11(homedir7(), ".npm", "bin", "opencode"),
8540
+ join12(homedir7(), ".opencode", "bin", "opencode"),
8541
+ join12(homedir7(), ".local", "bin", "opencode"),
8542
+ join12(homedir7(), ".npm", "bin", "opencode"),
8259
8543
  "/usr/local/bin/opencode",
8260
8544
  "/opt/homebrew/bin/opencode"
8261
8545
  ];
@@ -8271,7 +8555,7 @@ function findOpencodeBinary() {
8271
8555
  } catch {
8272
8556
  }
8273
8557
  for (const path of OPENCODE_FALLBACK_PATHS) {
8274
- if (existsSync10(path)) return path;
8558
+ if (existsSync11(path)) return path;
8275
8559
  }
8276
8560
  return null;
8277
8561
  }
@@ -9929,9 +10213,9 @@ ${pc6.bold("Device code (works on SSH/VPS):")}
9929
10213
 
9930
10214
  // src/agents/codex/app-launch.ts
9931
10215
  import { execSync as execSync3, spawn as spawn4 } from "child_process";
9932
- import { existsSync as existsSync11, readdirSync, statSync as statSync3 } from "fs";
10216
+ import { existsSync as existsSync12, readdirSync, statSync as statSync3 } from "fs";
9933
10217
  import { homedir as homedir8 } from "os";
9934
- import { join as join12 } from "path";
10218
+ import { join as join13 } from "path";
9935
10219
  import * as p6 from "@clack/prompts";
9936
10220
  var CODEX_BUNDLE_ID = "com.openai.codex";
9937
10221
  var DARWIN_APP_NAMES = ["ChatGPT", "Codex"];
@@ -9950,33 +10234,33 @@ function runPowerShell(script) {
9950
10234
  function darwinAppCandidates() {
9951
10235
  return DARWIN_APP_NAMES.flatMap((name) => [
9952
10236
  `/Applications/${name}.app`,
9953
- join12(homedir8(), "Applications", `${name}.app`)
10237
+ join13(homedir8(), "Applications", `${name}.app`)
9954
10238
  ]);
9955
10239
  }
9956
10240
  function winLocalAppData() {
9957
- return process.env.LOCALAPPDATA ?? join12(homedir8(), "AppData", "Local");
10241
+ return process.env.LOCALAPPDATA ?? join13(homedir8(), "AppData", "Local");
9958
10242
  }
9959
10243
  function winCodexExeCandidates() {
9960
10244
  const local = winLocalAppData();
9961
10245
  const bases = WIN_APP_NAMES.flatMap((name) => [
9962
- join12(local, "Programs", name),
9963
- join12(local, "Programs", `OpenAI ${name}`),
9964
- join12(local, name),
9965
- join12(local, `OpenAI ${name}`),
9966
- join12(local, "OpenAI", name)
10246
+ join13(local, "Programs", name),
10247
+ join13(local, "Programs", `OpenAI ${name}`),
10248
+ join13(local, name),
10249
+ join13(local, `OpenAI ${name}`),
10250
+ join13(local, "OpenAI", name)
9967
10251
  ]);
9968
- bases.push(join12(local, "openai-codex-electron"), join12(local, "openai-chatgpt-electron"));
10252
+ bases.push(join13(local, "openai-codex-electron"), join13(local, "openai-chatgpt-electron"));
9969
10253
  const out = [];
9970
10254
  for (const base of bases) {
9971
10255
  for (const name of WIN_APP_NAMES) {
9972
- out.push(join12(base, `${name}.exe`));
10256
+ out.push(join13(base, `${name}.exe`));
9973
10257
  }
9974
10258
  try {
9975
- if (existsSync11(base)) {
10259
+ if (existsSync12(base)) {
9976
10260
  for (const dir of readdirSync(base)) {
9977
10261
  if (dir.startsWith("app-")) {
9978
10262
  for (const name of WIN_APP_NAMES) {
9979
- out.push(join12(base, dir, `${name}.exe`));
10263
+ out.push(join13(base, dir, `${name}.exe`));
9980
10264
  }
9981
10265
  }
9982
10266
  }
@@ -9990,7 +10274,7 @@ function mdfindCodexApp() {
9990
10274
  try {
9991
10275
  const out = run(`mdfind "kMDItemCFBundleIdentifier == '${CODEX_BUNDLE_ID}'"`);
9992
10276
  const first = out.split("\n").map((l) => l.trim()).find(Boolean);
9993
- return first && existsSync11(first) ? first : null;
10277
+ return first && existsSync12(first) ? first : null;
9994
10278
  } catch {
9995
10279
  return null;
9996
10280
  }
@@ -9998,14 +10282,14 @@ function mdfindCodexApp() {
9998
10282
  function findCodexApp() {
9999
10283
  if (process.platform === "darwin") {
10000
10284
  for (const path of darwinAppCandidates()) {
10001
- if (existsSync11(path)) return path;
10285
+ if (existsSync12(path)) return path;
10002
10286
  }
10003
10287
  return mdfindCodexApp();
10004
10288
  }
10005
10289
  if (process.platform === "win32") {
10006
10290
  for (const path of winCodexExeCandidates()) {
10007
10291
  try {
10008
- if (existsSync11(path) && statSync3(path).isFile()) return path;
10292
+ if (existsSync12(path) && statSync3(path).isFile()) return path;
10009
10293
  } catch {
10010
10294
  }
10011
10295
  }
@@ -10151,9 +10435,9 @@ function codexAppInstallHint() {
10151
10435
 
10152
10436
  // src/agents/claude/desktop-launch.ts
10153
10437
  import { execSync as execSync4, spawn as spawn5 } from "child_process";
10154
- import { existsSync as existsSync12, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10438
+ import { existsSync as existsSync13, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
10155
10439
  import { homedir as homedir9 } from "os";
10156
- import { join as join13 } from "path";
10440
+ import { join as join14 } from "path";
10157
10441
  import * as p7 from "@clack/prompts";
10158
10442
  var CLAUDE_BUNDLE_ID = "com.anthropic.claudefordesktop";
10159
10443
  function claudeAppSupported() {
@@ -10170,26 +10454,26 @@ function runPowerShell2(script) {
10170
10454
  function darwinAppCandidates2() {
10171
10455
  return [
10172
10456
  "/Applications/Claude.app",
10173
- join13(homedir9(), "Applications", "Claude.app")
10457
+ join14(homedir9(), "Applications", "Claude.app")
10174
10458
  ];
10175
10459
  }
10176
10460
  function winLocalAppData2() {
10177
- return process.env.LOCALAPPDATA ?? join13(homedir9(), "AppData", "Local");
10461
+ return process.env.LOCALAPPDATA ?? join14(homedir9(), "AppData", "Local");
10178
10462
  }
10179
10463
  function winClaudeExeCandidates() {
10180
10464
  const local = winLocalAppData2();
10181
10465
  const bases = [
10182
- join13(local, "Programs", "Claude"),
10183
- join13(local, "Claude")
10466
+ join14(local, "Programs", "Claude"),
10467
+ join14(local, "Claude")
10184
10468
  ];
10185
10469
  const out = [];
10186
10470
  for (const base of bases) {
10187
- out.push(join13(base, "Claude.exe"));
10471
+ out.push(join14(base, "Claude.exe"));
10188
10472
  try {
10189
- if (existsSync12(base)) {
10473
+ if (existsSync13(base)) {
10190
10474
  for (const name of readdirSync2(base)) {
10191
10475
  if (name.startsWith("app-")) {
10192
- out.push(join13(base, name, "Claude.exe"));
10476
+ out.push(join14(base, name, "Claude.exe"));
10193
10477
  }
10194
10478
  }
10195
10479
  }
@@ -10202,7 +10486,7 @@ function mdfindClaudeApp() {
10202
10486
  try {
10203
10487
  const out = run2(`mdfind "kMDItemCFBundleIdentifier == '${CLAUDE_BUNDLE_ID}'"`);
10204
10488
  const first = out.split("\n").map((l) => l.trim()).find(Boolean);
10205
- return first && existsSync12(first) ? first : null;
10489
+ return first && existsSync13(first) ? first : null;
10206
10490
  } catch {
10207
10491
  return null;
10208
10492
  }
@@ -10210,14 +10494,14 @@ function mdfindClaudeApp() {
10210
10494
  function findClaudeApp() {
10211
10495
  if (process.platform === "darwin") {
10212
10496
  for (const path of darwinAppCandidates2()) {
10213
- if (existsSync12(path)) return path;
10497
+ if (existsSync13(path)) return path;
10214
10498
  }
10215
10499
  return mdfindClaudeApp();
10216
10500
  }
10217
10501
  if (process.platform === "win32") {
10218
10502
  for (const path of winClaudeExeCandidates()) {
10219
10503
  try {
10220
- if (existsSync12(path) && statSync4(path).isFile()) return path;
10504
+ if (existsSync13(path) && statSync4(path).isFile()) return path;
10221
10505
  } catch {
10222
10506
  }
10223
10507
  }
@@ -10489,6 +10773,8 @@ export {
10489
10773
  encodeToolUseId,
10490
10774
  serializeToolResultContent,
10491
10775
  translateRequest,
10776
+ recordUsage,
10777
+ aggregateAnalytics,
10492
10778
  aliasModelId,
10493
10779
  startProxyCatalog,
10494
10780
  startProxy,
@@ -10556,4 +10842,4 @@ export {
10556
10842
  quitClaudeAppGracefully,
10557
10843
  launchOrRestartClaudeApp
10558
10844
  };
10559
- //# sourceMappingURL=chunk-QPXRFBQI.js.map
10845
+ //# sourceMappingURL=chunk-CH5IEXJN.js.map