@llmops/app 0.1.3 → 0.1.5-beta.2

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 (106) hide show
  1. package/dist/.vite/manifest.json +535 -168
  2. package/dist/assets/Form-B1rZ0Hvr.js +1 -0
  3. package/dist/assets/InternalBackdrop-CXjGP6a9.js +12 -0
  4. package/dist/assets/_environment-zVjmOhBI.js +1 -0
  5. package/dist/assets/_observability-CQ4Ll_WT.js +1 -0
  6. package/dist/assets/_observability-DPSeR4-h.css +1 -0
  7. package/dist/assets/_settings-B87EsM9n.css +1 -0
  8. package/dist/assets/_settings-Bv8acMq8.js +1 -0
  9. package/dist/assets/{_tabs-DqcWXw0k.js → _tabs-Bi-rsAVV.js} +1 -1
  10. package/dist/assets/{_tabs-f2ku-dN-.js → _tabs-oKfgxR5m.js} +1 -1
  11. package/dist/assets/_variant-Zg5HjNSN.js +30 -0
  12. package/dist/assets/{_variants-B0pPx_AW.js → _variants-B36Lo8m_.js} +1 -1
  13. package/dist/assets/area.css-DTnbbu5M.js +1 -0
  14. package/dist/assets/button-DftzA6-1.js +1 -0
  15. package/dist/assets/check-PaWYhFn6.js +1 -0
  16. package/dist/assets/chevron-down-BjDiaa62.js +1 -0
  17. package/dist/assets/{chevron-right-C4hlJkjh.js → chevron-right-Dz77YeP_.js} +1 -1
  18. package/dist/assets/configs-CtNuMmVR.js +1 -0
  19. package/dist/assets/{copy-8RnLMadS.js → copy-D-DZB-6Z.js} +1 -1
  20. package/dist/assets/costs-DwVSRTwW.js +1 -0
  21. package/dist/assets/en-US-C8ut0f5H.js +1 -0
  22. package/dist/assets/environments-BB95BiC0.js +1 -0
  23. package/dist/assets/formatDistance-Bok3-MB1.js +1 -0
  24. package/dist/assets/getDisabledMountTransitionStyles-CKUvhO4q.js +1 -0
  25. package/dist/assets/index-BCY9aD4r.js +16 -0
  26. package/dist/assets/index-BS3B2BG5.js +1 -0
  27. package/dist/assets/{index-CzBp_9q8.css → index-BiCR_Kj2.css} +1 -1
  28. package/dist/assets/index-CnOYeKc1.js +1 -0
  29. package/dist/assets/index-CpAxLUl2.js +1 -0
  30. package/dist/assets/index-D841new6.js +1 -0
  31. package/dist/assets/index-DvzXLQL6.js +4 -0
  32. package/dist/assets/index-xdhK5tU9.js +1 -0
  33. package/dist/assets/{index.esm-Du3lv78y.js → index.esm-COTq2pHX.js} +1 -1
  34. package/dist/assets/info-box-JXSyJ9E4.css +1 -0
  35. package/dist/assets/info-box.css-HIaMvhbC.js +1 -0
  36. package/dist/assets/llmops-B3IIte87.css +1 -0
  37. package/dist/assets/llmops-C5uyvq6E.js +1 -0
  38. package/dist/assets/observability-C5jdcUSg.css +1 -0
  39. package/dist/assets/observability.css-dztiak5K.js +1 -0
  40. package/dist/assets/overview-BwCF8A1G.js +1 -0
  41. package/dist/assets/{plus-BNVL12hi.js → plus-C_L0BpbU.js} +1 -1
  42. package/dist/assets/popover-CSR0ctop.js +1 -0
  43. package/dist/assets/popupStateMapping-DkOpwBhG.js +1 -0
  44. package/dist/assets/requests-D8wE_E2M.js +1 -0
  45. package/dist/assets/{route-DCt595Gm.js → route-CI77gokb.js} +1 -1
  46. package/dist/assets/{route-D_ra2qKi.js → route-CZ2yCnzN.js} +1 -1
  47. package/dist/assets/route-vfMzB8KR.js +1 -0
  48. package/dist/assets/route-wrnLt3bn.js +1 -0
  49. package/dist/assets/secrets-B9u5jvFm.js +1 -0
  50. package/dist/assets/settings-24qWHZEq.js +1 -0
  51. package/dist/assets/settings-BLt538aO.js +1 -0
  52. package/dist/assets/{table-6j4oSY37.js → table-xLf4iXV8.js} +1 -1
  53. package/dist/assets/{tabs.css-BDs200M3.js → tabs.css-fttLH8Xj.js} +1 -1
  54. package/dist/assets/{targeting-BOZ8PRbu.js → targeting-DZIwwaYV.js} +1 -1
  55. package/dist/assets/tooltip-BKoDpdAC.js +1 -0
  56. package/dist/assets/update-or-create-name-ChWLK2jI.js +1 -0
  57. package/dist/assets/useButton-Ct8hIyjz.js +1 -0
  58. package/dist/assets/useConfigList-Du__Gu7q.js +1 -0
  59. package/dist/assets/{useConfigVariants-DoAiT4HN.js → useConfigVariants-C8frrCcZ.js} +1 -1
  60. package/dist/assets/useEnvironments-CYkrqAdO.js +1 -0
  61. package/dist/assets/useFocus-Du1aH2j1.js +1 -0
  62. package/dist/assets/{useMutation-B5RT6zjK.js → useMutation-VUB-GXkv.js} +1 -1
  63. package/dist/assets/usePopupAutoResize-ddPHogUJ.js +1 -0
  64. package/dist/assets/useRole-DP91D1OX.js +1 -0
  65. package/dist/assets/useSetTargeting-CQWutQzN.css +1 -0
  66. package/dist/assets/useSetTargeting-D1rPM8vz.js +1 -0
  67. package/dist/assets/useSyncedFloatingRootContext-BzuhbdSw.js +1 -0
  68. package/dist/assets/useTargetingRules-DS57orU0.js +1 -0
  69. package/dist/assets/useValueChanged-vz8uKgCk.js +1 -0
  70. package/dist/assets/user-profile-Bt3_D9Gr.js +1 -0
  71. package/dist/assets/variants-CYXSNKzX.js +1 -0
  72. package/dist/assets/variants.css-BjGKD3Nv.js +1 -0
  73. package/dist/assets/workspace-general-BRMpUqb-.js +1 -0
  74. package/dist/assets/workspace-general-ET4CEZQV.css +1 -0
  75. package/dist/index.cjs +763 -23
  76. package/dist/index.mjs +764 -24
  77. package/package.json +3 -3
  78. package/dist/assets/Form-CR9iKNJV.js +0 -1
  79. package/dist/assets/_environment-DUWlgl3c.js +0 -1
  80. package/dist/assets/_variant-D5crm4Ds.js +0 -30
  81. package/dist/assets/button-DO9sNXVT.js +0 -1
  82. package/dist/assets/check-DqI6mrEe.js +0 -1
  83. package/dist/assets/configs-D7rrXGVw.js +0 -1
  84. package/dist/assets/environments-CQ_2r1iM.js +0 -1
  85. package/dist/assets/formatDistance-fxR--uRk.js +0 -1
  86. package/dist/assets/index-8q56yhPq.js +0 -16
  87. package/dist/assets/index-CAAXzvj0.js +0 -1
  88. package/dist/assets/index-Cr1VB5iR.js +0 -1
  89. package/dist/assets/index-Dd3OazzV.js +0 -1
  90. package/dist/assets/new-config-state-B8sMe-TC.css +0 -1
  91. package/dist/assets/new-config-state.css-BkrgBLVT.js +0 -1
  92. package/dist/assets/secrets-DoNAc8Mo.js +0 -1
  93. package/dist/assets/settings-C9bl_jrl.js +0 -1
  94. package/dist/assets/settings-CdtZpv34.js +0 -1
  95. package/dist/assets/update-or-create-name-B72U1gm0.js +0 -4
  96. package/dist/assets/useButton-CAZmhHhe.js +0 -1
  97. package/dist/assets/useConfigList-DVy2f9Ka.js +0 -1
  98. package/dist/assets/useEnvironments-B9zdgoPZ.js +0 -1
  99. package/dist/assets/useRole-BBEFofTs.js +0 -1
  100. package/dist/assets/useSetTargeting-DIyb-zyZ.css +0 -1
  101. package/dist/assets/useSetTargeting-x-2mNQAV.js +0 -1
  102. package/dist/assets/useTargetingRules-DWyIfOXq.js +0 -1
  103. package/dist/assets/useValueChanged-DLCw33hO.js +0 -12
  104. package/dist/assets/variants-CLcQicJ2.js +0 -1
  105. package/dist/assets/variants.css-CRQAZpOT.js +0 -1
  106. /package/dist/assets/{formatDistance-DoOD1Loz.css → area-DoOD1Loz.css} +0 -0
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Hono } from "hono";
2
- import { SupportedProviders, createDataLayer, generateId, validateLLMOpsConfig, variantJsonDataSchema } from "@llmops/core";
2
+ import { SupportedProviders, createDataLayer, generateId, logger, validateLLMOpsConfig, variantJsonDataSchema } from "@llmops/core";
3
3
  import reactServer from "react-dom/server";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { fileURLToPath } from "node:url";
@@ -14,6 +14,7 @@ import { zValidator } from "@hono/zod-validator";
14
14
  import { prettyJSON } from "hono/pretty-json";
15
15
  import { HTTPException } from "hono/http-exception";
16
16
  import { cors } from "hono/cors";
17
+ import { randomUUID } from "node:crypto";
17
18
  import gateway from "@llmops/gateway";
18
19
  import { env } from "node:process";
19
20
  import { createDatabaseFromConnection } from "@llmops/core/db";
@@ -36,7 +37,7 @@ var __export = (all, symbols) => {
36
37
 
37
38
  //#endregion
38
39
  //#region src/client/index.tsx?url
39
- var client_default = "/assets/index-8q56yhPq.js";
40
+ var client_default = "/assets/index-BCY9aD4r.js";
40
41
 
41
42
  //#endregion
42
43
  //#region src/client/styles/styles.css?url
@@ -67,7 +68,7 @@ const ReactRefresh = () => {
67
68
  const { renderToString } = reactServer;
68
69
  const manifestPath = join(dirname(fileURLToPath(import.meta.url)), "./.vite/manifest.json");
69
70
  const manifest = existsSync(manifestPath) ? JSON.parse(readFileSync(manifestPath, "utf-8")) : {};
70
- const renderer = ({ basePath = "", dev = false, llmProviders }) => {
71
+ const renderer = ({ basePath = "", dev = false, llmProviders, authType }) => {
71
72
  const stylesPath = basePath === "/" ? styles_default : basePath + styles_default;
72
73
  const clientPath = basePath === "/" ? client_default : basePath + client_default;
73
74
  const faviconPath = basePath === "/" ? "/favicon.ico" : basePath + "/assets/favicon.ico";
@@ -330,7 +331,8 @@ const renderer = ({ basePath = "", dev = false, llmProviders }) => {
330
331
  /* @__PURE__ */ jsx("script", { children: `
331
332
  window.bootstrapData = {
332
333
  basePath: "${basePath}",
333
- llmProviders: ${JSON.stringify(llmProviders || [])}
334
+ llmProviders: ${JSON.stringify(llmProviders || [])},
335
+ authType: "${authType || "basic"}"
334
336
  };
335
337
  ` }),
336
338
  dev && /* @__PURE__ */ jsx(ReactRefresh, {})
@@ -12681,11 +12683,13 @@ var zod_default = external_exports;
12681
12683
  //#endregion
12682
12684
  //#region src/server/lib/zv.ts
12683
12685
  const zv = (target, schema) => zValidator(target, schema, (result, c) => {
12684
- if (!result.success)
12685
- /**
12686
- * @todo handle error properly
12687
- */
12688
- return c.json({ message: "Bad Request" }, 400);
12686
+ if (!result.success) return c.json({
12687
+ message: "Bad Request",
12688
+ errors: result.error.issues.map((issue$1) => ({
12689
+ path: issue$1.path.join("."),
12690
+ message: issue$1.message
12691
+ }))
12692
+ }, 400);
12689
12693
  });
12690
12694
 
12691
12695
  //#endregion
@@ -12712,9 +12716,228 @@ const internalServerError = (message, code) => {
12712
12716
  };
12713
12717
  };
12714
12718
 
12719
+ //#endregion
12720
+ //#region src/server/handlers/analytics/index.ts
12721
+ /**
12722
+ * Convert micro-dollars to formatted dollar string
12723
+ */
12724
+ function formatCost(microDollars, decimals = 6) {
12725
+ return `$${(microDollars / 1e6).toFixed(decimals)}`;
12726
+ }
12727
+ /**
12728
+ * Parse ISO date string to Date object
12729
+ * Accepts both ISO strings (2026-01-02T10:30:00.000Z) and date-only strings (2026-01-02)
12730
+ */
12731
+ function parseDate(dateStr) {
12732
+ const date$4 = new Date(dateStr);
12733
+ if (isNaN(date$4.getTime())) throw new Error(`Invalid date string: ${dateStr}`);
12734
+ return date$4;
12735
+ }
12736
+ /**
12737
+ * Parse date string for start of range
12738
+ * - ISO strings are used as-is
12739
+ * - Date-only strings (YYYY-MM-DD) are treated as start of day UTC
12740
+ */
12741
+ function parseStartDate(dateStr) {
12742
+ return parseDate(dateStr);
12743
+ }
12744
+ /**
12745
+ * Parse date string for end of range
12746
+ * - ISO strings are used as-is
12747
+ * - Date-only strings (YYYY-MM-DD) are set to end of day (23:59:59.999 UTC)
12748
+ */
12749
+ function parseEndDate(dateStr) {
12750
+ const date$4 = parseDate(dateStr);
12751
+ if (!dateStr.includes("T")) date$4.setUTCHours(23, 59, 59, 999);
12752
+ return date$4;
12753
+ }
12754
+ /**
12755
+ * Zod schema for ISO date strings
12756
+ * Validates that the string can be parsed as a valid date
12757
+ */
12758
+ const isoDateString = zod_default.string().refine((val) => !isNaN(new Date(val).getTime()), { message: "Invalid date format. Expected ISO 8601 string (e.g., 2026-01-02T10:30:00.000Z) or date string (e.g., 2026-01-02)" });
12759
+ /**
12760
+ * Date range query schema
12761
+ * Accepts ISO 8601 date strings or date-only strings (YYYY-MM-DD)
12762
+ * - startDate: Used as-is for ISO strings, start of day for date-only
12763
+ * - endDate: Used as-is for ISO strings, end of day (23:59:59.999) for date-only
12764
+ */
12765
+ const dateRangeSchema = zod_default.object({
12766
+ startDate: isoDateString.transform(parseStartDate),
12767
+ endDate: isoDateString.transform(parseEndDate)
12768
+ });
12769
+ /**
12770
+ * Analytics API routes for cost and usage tracking
12771
+ */
12772
+ const app$10 = new Hono().get("/requests", zv("query", zod_default.object({
12773
+ limit: zod_default.string().transform(Number).optional(),
12774
+ offset: zod_default.string().transform(Number).optional(),
12775
+ configId: zod_default.string().uuid().optional(),
12776
+ provider: zod_default.string().optional(),
12777
+ model: zod_default.string().optional(),
12778
+ startDate: isoDateString.optional(),
12779
+ endDate: isoDateString.optional()
12780
+ })), async (c) => {
12781
+ const db = c.get("db");
12782
+ const query = c.req.valid("query");
12783
+ try {
12784
+ const requests = await db.listRequests({
12785
+ limit: query.limit,
12786
+ offset: query.offset,
12787
+ configId: query.configId,
12788
+ provider: query.provider,
12789
+ model: query.model,
12790
+ startDate: query.startDate ? parseStartDate(query.startDate) : void 0,
12791
+ endDate: query.endDate ? parseEndDate(query.endDate) : void 0
12792
+ });
12793
+ return c.json(successResponse(requests, 200));
12794
+ } catch (error$45) {
12795
+ console.error("Error fetching requests:", error$45);
12796
+ return c.json(internalServerError("Failed to fetch requests", 500), 500);
12797
+ }
12798
+ }).get("/requests/:requestId", zv("param", zod_default.object({ requestId: zod_default.string().uuid() })), async (c) => {
12799
+ const db = c.get("db");
12800
+ const { requestId } = c.req.valid("param");
12801
+ try {
12802
+ const request = await db.getRequestByRequestId(requestId);
12803
+ if (!request) return c.json({ error: "Request not found" }, 404);
12804
+ return c.json(successResponse(request, 200));
12805
+ } catch (error$45) {
12806
+ console.error("Error fetching request:", error$45);
12807
+ return c.json(internalServerError("Failed to fetch request", 500), 500);
12808
+ }
12809
+ }).get("/costs/total", zv("query", dateRangeSchema), async (c) => {
12810
+ const db = c.get("db");
12811
+ const { startDate, endDate } = c.req.valid("query");
12812
+ try {
12813
+ const data = await db.getTotalCost({
12814
+ startDate,
12815
+ endDate
12816
+ });
12817
+ if (!data) return c.json(successResponse({
12818
+ totalCost: 0,
12819
+ totalCostFormatted: "$0.000000",
12820
+ totalInputCost: 0,
12821
+ totalOutputCost: 0,
12822
+ totalPromptTokens: 0,
12823
+ totalCompletionTokens: 0,
12824
+ totalTokens: 0,
12825
+ requestCount: 0
12826
+ }, 200));
12827
+ return c.json(successResponse({
12828
+ ...data,
12829
+ totalCostFormatted: formatCost(data.totalCost),
12830
+ totalInputCostFormatted: formatCost(data.totalInputCost),
12831
+ totalOutputCostFormatted: formatCost(data.totalOutputCost)
12832
+ }, 200));
12833
+ } catch (error$45) {
12834
+ console.error("Error fetching total costs:", error$45);
12835
+ return c.json(internalServerError("Failed to fetch total costs", 500), 500);
12836
+ }
12837
+ }).get("/costs/by-model", zv("query", dateRangeSchema), async (c) => {
12838
+ const db = c.get("db");
12839
+ const { startDate, endDate } = c.req.valid("query");
12840
+ try {
12841
+ const data = await db.getCostByModel({
12842
+ startDate,
12843
+ endDate
12844
+ });
12845
+ return c.json(successResponse(data, 200));
12846
+ } catch (error$45) {
12847
+ console.error("Error fetching costs by model:", error$45);
12848
+ return c.json(internalServerError("Failed to fetch costs by model", 500), 500);
12849
+ }
12850
+ }).get("/costs/by-provider", zv("query", dateRangeSchema), async (c) => {
12851
+ const db = c.get("db");
12852
+ const { startDate, endDate } = c.req.valid("query");
12853
+ try {
12854
+ const data = await db.getCostByProvider({
12855
+ startDate,
12856
+ endDate
12857
+ });
12858
+ return c.json(successResponse(data, 200));
12859
+ } catch (error$45) {
12860
+ console.error("Error fetching costs by provider:", error$45);
12861
+ return c.json(internalServerError("Failed to fetch costs by provider", 500), 500);
12862
+ }
12863
+ }).get("/costs/by-config", zv("query", dateRangeSchema), async (c) => {
12864
+ const db = c.get("db");
12865
+ const { startDate, endDate } = c.req.valid("query");
12866
+ try {
12867
+ const data = await db.getCostByConfig({
12868
+ startDate,
12869
+ endDate
12870
+ });
12871
+ return c.json(successResponse(data, 200));
12872
+ } catch (error$45) {
12873
+ console.error("Error fetching costs by config:", error$45);
12874
+ return c.json(internalServerError("Failed to fetch costs by config", 500), 500);
12875
+ }
12876
+ }).get("/costs/daily", zv("query", dateRangeSchema), async (c) => {
12877
+ const db = c.get("db");
12878
+ const { startDate, endDate } = c.req.valid("query");
12879
+ try {
12880
+ const data = await db.getDailyCosts({
12881
+ startDate,
12882
+ endDate
12883
+ });
12884
+ return c.json(successResponse(data, 200));
12885
+ } catch (error$45) {
12886
+ console.error("Error fetching daily costs:", error$45);
12887
+ return c.json(internalServerError("Failed to fetch daily costs", 500), 500);
12888
+ }
12889
+ }).get("/costs/summary", zv("query", dateRangeSchema.extend({ groupBy: zod_default.enum([
12890
+ "day",
12891
+ "hour",
12892
+ "model",
12893
+ "provider",
12894
+ "config"
12895
+ ]).optional() })), async (c) => {
12896
+ const db = c.get("db");
12897
+ const { startDate, endDate, groupBy } = c.req.valid("query");
12898
+ try {
12899
+ const data = await db.getCostSummary({
12900
+ startDate,
12901
+ endDate,
12902
+ groupBy
12903
+ });
12904
+ return c.json(successResponse(data, 200));
12905
+ } catch (error$45) {
12906
+ console.error("Error fetching cost summary:", error$45);
12907
+ return c.json(internalServerError("Failed to fetch cost summary", 500), 500);
12908
+ }
12909
+ }).get("/stats", zv("query", dateRangeSchema), async (c) => {
12910
+ const db = c.get("db");
12911
+ const { startDate, endDate } = c.req.valid("query");
12912
+ try {
12913
+ const data = await db.getRequestStats({
12914
+ startDate,
12915
+ endDate
12916
+ });
12917
+ if (!data) return c.json(successResponse({
12918
+ totalRequests: 0,
12919
+ successfulRequests: 0,
12920
+ failedRequests: 0,
12921
+ streamingRequests: 0,
12922
+ avgLatencyMs: 0,
12923
+ maxLatencyMs: 0,
12924
+ minLatencyMs: 0,
12925
+ successRate: 0
12926
+ }, 200));
12927
+ return c.json(successResponse({
12928
+ ...data,
12929
+ successRate: data.totalRequests > 0 ? (data.successfulRequests / data.totalRequests * 100).toFixed(2) : 0
12930
+ }, 200));
12931
+ } catch (error$45) {
12932
+ console.error("Error fetching request stats:", error$45);
12933
+ return c.json(internalServerError("Failed to fetch request stats", 500), 500);
12934
+ }
12935
+ });
12936
+ var analytics_default = app$10;
12937
+
12715
12938
  //#endregion
12716
12939
  //#region src/server/handlers/configs/index.ts
12717
- const app$8 = new Hono().post("/", zv("json", zod_default.object({ name: zod_default.string().min(1) })), async (c) => {
12940
+ const app$9 = new Hono().post("/", zv("json", zod_default.object({ name: zod_default.string().min(1) })), async (c) => {
12718
12941
  const db = c.get("db");
12719
12942
  try {
12720
12943
  const value = await db.createNewConfig({ name: c.req.valid("json").name });
@@ -12818,7 +13041,7 @@ const app$8 = new Hono().post("/", zv("json", zod_default.object({ name: zod_def
12818
13041
  return c.json(internalServerError("Failed to delete config", 500), 500);
12819
13042
  }
12820
13043
  });
12821
- var configs_default = app$8;
13044
+ var configs_default = app$9;
12822
13045
 
12823
13046
  //#endregion
12824
13047
  //#region src/server/handlers/environments/index.ts
@@ -12829,7 +13052,7 @@ var configs_default = app$8;
12829
13052
  const generateSecretKey$1 = (slug) => {
12830
13053
  return `sec_${slug.slice(0, 4).toLowerCase()}_${generateId(24)}`;
12831
13054
  };
12832
- const app$7 = new Hono().post("/", zv("json", zod_default.object({
13055
+ const app$8 = new Hono().post("/", zv("json", zod_default.object({
12833
13056
  name: zod_default.string().min(1),
12834
13057
  slug: zod_default.string().min(1),
12835
13058
  isProd: zod_default.boolean().optional()
@@ -12914,7 +13137,7 @@ const app$7 = new Hono().post("/", zv("json", zod_default.object({
12914
13137
  return c.json(internalServerError("Failed to fetch environment secrets", 500), 500);
12915
13138
  }
12916
13139
  });
12917
- var environments_default = app$7;
13140
+ var environments_default = app$8;
12918
13141
 
12919
13142
  //#endregion
12920
13143
  //#region src/server/handlers/providers/index.ts
@@ -12934,7 +13157,7 @@ async function fetchModelsDevData() {
12934
13157
  cacheTimestamp = now;
12935
13158
  return modelsCache;
12936
13159
  }
12937
- const app$6 = new Hono().get("/", async (c) => {
13160
+ const app$7 = new Hono().get("/", async (c) => {
12938
13161
  try {
12939
13162
  const data = await fetchModelsDevData();
12940
13163
  const providers = Object.values(data).map((provider) => ({
@@ -13042,11 +13265,11 @@ const app$6 = new Hono().get("/", async (c) => {
13042
13265
  return c.json(internalServerError("Failed to fetch models", 500), 500);
13043
13266
  }
13044
13267
  });
13045
- var providers_default = app$6;
13268
+ var providers_default = app$7;
13046
13269
 
13047
13270
  //#endregion
13048
13271
  //#region src/server/handlers/targeting/index.ts
13049
- const app$5 = new Hono().post("/", zv("json", zod_default.object({
13272
+ const app$6 = new Hono().post("/", zv("json", zod_default.object({
13050
13273
  environmentId: zod_default.string().uuid(),
13051
13274
  configId: zod_default.string().uuid(),
13052
13275
  configVariantId: zod_default.string().uuid(),
@@ -13173,11 +13396,11 @@ const app$5 = new Hono().post("/", zv("json", zod_default.object({
13173
13396
  return c.json(internalServerError("Failed to set targeting for environment", 500), 500);
13174
13397
  }
13175
13398
  });
13176
- var targeting_default = app$5;
13399
+ var targeting_default = app$6;
13177
13400
 
13178
13401
  //#endregion
13179
13402
  //#region src/server/handlers/variants.ts
13180
- const app$4 = new Hono().get("/:id", zv("param", zod_default.object({ id: zod_default.string().uuid() })), async (c) => {
13403
+ const app$5 = new Hono().get("/:id", zv("param", zod_default.object({ id: zod_default.string().uuid() })), async (c) => {
13181
13404
  const db = c.get("db");
13182
13405
  const { id } = c.req.valid("param");
13183
13406
  const versionParam = c.req.query("version");
@@ -13324,7 +13547,31 @@ const app$4 = new Hono().get("/:id", zv("param", zod_default.object({ id: zod_de
13324
13547
  return c.json(internalServerError("Failed to fetch variant version", 500), 500);
13325
13548
  }
13326
13549
  });
13327
- var variants_default = app$4;
13550
+ var variants_default = app$5;
13551
+
13552
+ //#endregion
13553
+ //#region src/server/handlers/workspace-settings/index.ts
13554
+ const app$4 = new Hono().get("/", async (c) => {
13555
+ const db = c.get("db");
13556
+ try {
13557
+ const settings = await db.getWorkspaceSettings();
13558
+ return c.json(successResponse(settings, 200));
13559
+ } catch (error$45) {
13560
+ console.error("Error fetching workspace settings:", error$45);
13561
+ return c.json(internalServerError("Failed to fetch workspace settings", 500), 500);
13562
+ }
13563
+ }).patch("/", zv("json", zod_default.object({ name: zod_default.string().nullable().optional() })), async (c) => {
13564
+ const db = c.get("db");
13565
+ const body = c.req.valid("json");
13566
+ try {
13567
+ const settings = await db.updateWorkspaceSettings(body);
13568
+ return c.json(successResponse(settings, 200));
13569
+ } catch (error$45) {
13570
+ console.error("Error updating workspace settings:", error$45);
13571
+ return c.json(internalServerError("Failed to update workspace settings", 500), 500);
13572
+ }
13573
+ });
13574
+ var workspace_settings_default = app$4;
13328
13575
 
13329
13576
  //#endregion
13330
13577
  //#region src/server/handlers/v1.ts
@@ -13345,7 +13592,7 @@ const app$3 = new Hono().use("*", async (c, next) => {
13345
13592
  error: "Auth middleware not configured",
13346
13593
  message: `Auth type "${config$1.auth.type}" requires @llmops/enterprise middleware. Either use basicAuth() from @llmops/sdk or install @llmops/enterprise and add the auth middleware.`
13347
13594
  }, 501);
13348
- }).route("/configs", configs_default).route("/environments", environments_default).route("/providers", providers_default).route("/targeting", targeting_default).route("/variants", variants_default);
13595
+ }).route("/analytics", analytics_default).route("/configs", configs_default).route("/environments", environments_default).route("/providers", providers_default).route("/targeting", targeting_default).route("/variants", variants_default).route("/workspace-settings", workspace_settings_default);
13349
13596
  var v1_default = app$3;
13350
13597
 
13351
13598
  //#endregion
@@ -13515,7 +13762,7 @@ const createGatewayAdapterMiddleware = () => {
13515
13762
  if (method === "POST" && contentType === "application/json" && (path.endsWith("/chat/completions") || path.endsWith("/completions"))) {
13516
13763
  const mergedBody = mergeChatCompletionBody(await c.req.json(), variantConfig, data.modelName);
13517
13764
  const newHeaders = new Headers(c.req.raw.headers);
13518
- newHeaders.set("x-portkey-config", JSON.stringify(portkeyConfig));
13765
+ newHeaders.set("x-llmops-config", JSON.stringify(portkeyConfig));
13519
13766
  const newRequest = new Request(c.req.raw.url, {
13520
13767
  method: c.req.raw.method,
13521
13768
  headers: newHeaders,
@@ -13528,9 +13775,11 @@ const createGatewayAdapterMiddleware = () => {
13528
13775
  configurable: true
13529
13776
  });
13530
13777
  c.req.bodyCache = {};
13531
- } else c.req.raw.headers.set("x-portkey-config", JSON.stringify(portkeyConfig));
13778
+ } else c.req.raw.headers.set("x-llmops-config", JSON.stringify(portkeyConfig));
13532
13779
  c.set("variantConfig", variantConfig);
13533
13780
  c.set("variantModel", variantConfig.model || data.modelName);
13781
+ c.set("configId", data.configId);
13782
+ c.set("variantId", data.variantId);
13534
13783
  await next();
13535
13784
  } catch (error$45) {
13536
13785
  console.error("Gateway adapter error:", error$45);
@@ -13542,12 +13791,501 @@ const createGatewayAdapterMiddleware = () => {
13542
13791
  };
13543
13792
  };
13544
13793
 
13794
+ //#endregion
13795
+ //#region src/server/lib/streamingCostExtractor.ts
13796
+ /**
13797
+ * Creates a TransformStream that passes through SSE data while extracting usage info.
13798
+ *
13799
+ * @param onComplete - Callback invoked when stream completes with extracted usage
13800
+ * @returns TransformStream that passes through the original stream
13801
+ *
13802
+ * @example
13803
+ * ```typescript
13804
+ * const { stream, usagePromise } = createStreamingCostExtractor();
13805
+ *
13806
+ * // Pipe the response through the extractor
13807
+ * const transformedResponse = originalResponse.body.pipeThrough(stream);
13808
+ *
13809
+ * // Later, get the usage
13810
+ * const usage = await usagePromise;
13811
+ * if (usage) {
13812
+ * console.log(`Tokens used: ${usage.totalTokens}`);
13813
+ * }
13814
+ * ```
13815
+ */
13816
+ function createStreamingCostExtractor() {
13817
+ let extractedUsage = null;
13818
+ let buffer = "";
13819
+ let resolveUsage;
13820
+ const usagePromise = new Promise((resolve) => {
13821
+ resolveUsage = resolve;
13822
+ });
13823
+ const decoder = new TextDecoder();
13824
+ return {
13825
+ stream: new TransformStream({
13826
+ transform(chunk, controller) {
13827
+ controller.enqueue(chunk);
13828
+ const text = decoder.decode(chunk, { stream: true });
13829
+ buffer += text;
13830
+ const messages = buffer.split("\n\n");
13831
+ buffer = messages.pop() || "";
13832
+ for (const message of messages) {
13833
+ const trimmed = message.trim();
13834
+ if (!trimmed) continue;
13835
+ if (!trimmed.startsWith("data:")) continue;
13836
+ const jsonPart = trimmed.slice(5).trim();
13837
+ if (jsonPart === "[DONE]") continue;
13838
+ try {
13839
+ const parsed = JSON.parse(jsonPart);
13840
+ if (parsed.usage) extractedUsage = {
13841
+ promptTokens: parsed.usage.prompt_tokens ?? 0,
13842
+ completionTokens: parsed.usage.completion_tokens ?? 0,
13843
+ totalTokens: parsed.usage.total_tokens ?? 0,
13844
+ cachedTokens: parsed.usage.prompt_tokens_details?.cached_tokens
13845
+ };
13846
+ } catch {}
13847
+ }
13848
+ },
13849
+ flush(controller) {
13850
+ if (buffer.trim()) {
13851
+ const trimmed = buffer.trim();
13852
+ if (trimmed.startsWith("data:")) {
13853
+ const jsonPart = trimmed.slice(5).trim();
13854
+ if (jsonPart !== "[DONE]") try {
13855
+ const parsed = JSON.parse(jsonPart);
13856
+ if (parsed.usage) extractedUsage = {
13857
+ promptTokens: parsed.usage.prompt_tokens ?? 0,
13858
+ completionTokens: parsed.usage.completion_tokens ?? 0,
13859
+ totalTokens: parsed.usage.total_tokens ?? 0,
13860
+ cachedTokens: parsed.usage.prompt_tokens_details?.cached_tokens
13861
+ };
13862
+ } catch {}
13863
+ }
13864
+ }
13865
+ resolveUsage(extractedUsage);
13866
+ }
13867
+ }),
13868
+ usagePromise
13869
+ };
13870
+ }
13871
+ /**
13872
+ * Wraps a Response with a streaming body to extract usage information.
13873
+ *
13874
+ * @param response - Original streaming Response
13875
+ * @returns Object with transformed response and promise for usage data
13876
+ *
13877
+ * @example
13878
+ * ```typescript
13879
+ * const result = wrapStreamingResponse(originalResponse);
13880
+ *
13881
+ * // Return the transformed response to the client
13882
+ * return result.response;
13883
+ *
13884
+ * // After response is sent, get usage for cost tracking
13885
+ * result.usagePromise.then((usage) => {
13886
+ * if (usage) {
13887
+ * trackCost(usage);
13888
+ * }
13889
+ * });
13890
+ * ```
13891
+ */
13892
+ function wrapStreamingResponse(response) {
13893
+ if (!response.body) return {
13894
+ response,
13895
+ usagePromise: Promise.resolve(null)
13896
+ };
13897
+ const { stream, usagePromise } = createStreamingCostExtractor();
13898
+ const transformedBody = response.body.pipeThrough(stream);
13899
+ return {
13900
+ response: new Response(transformedBody, {
13901
+ status: response.status,
13902
+ statusText: response.statusText,
13903
+ headers: response.headers
13904
+ }),
13905
+ usagePromise
13906
+ };
13907
+ }
13908
+ /**
13909
+ * Ensures stream_options.include_usage is set for cost tracking
13910
+ * Modifies the body in place.
13911
+ *
13912
+ * @param body - Request body (will be modified)
13913
+ * @returns Modified body with include_usage enabled
13914
+ */
13915
+ function ensureStreamUsageEnabled(body) {
13916
+ if (body.stream === true) body.stream_options = {
13917
+ ...body.stream_options || {},
13918
+ include_usage: true
13919
+ };
13920
+ return body;
13921
+ }
13922
+
13923
+ //#endregion
13924
+ //#region src/server/services/batchWriter.ts
13925
+ /**
13926
+ * Creates a BatchWriter instance
13927
+ *
13928
+ * @example
13929
+ * ```typescript
13930
+ * const writer = createBatchWriter(
13931
+ * { batchInsertRequests: db.batchInsertRequests },
13932
+ * { flushIntervalMs: 2000 }
13933
+ * );
13934
+ *
13935
+ * // Enqueue a request
13936
+ * writer.enqueue({
13937
+ * requestId: 'req-123',
13938
+ * provider: 'openai',
13939
+ * model: 'gpt-4o',
13940
+ * // ... other fields
13941
+ * });
13942
+ *
13943
+ * // When shutting down
13944
+ * await writer.stop();
13945
+ * ```
13946
+ */
13947
+ function createBatchWriter(deps, config$1 = {}) {
13948
+ const { flushIntervalMs = 2e3, maxBatchSize = 100, debug = false } = config$1;
13949
+ let queue = [];
13950
+ let flushTimer = null;
13951
+ let running = false;
13952
+ let flushing = false;
13953
+ const log = debug ? (msg) => logger.debug(msg) : () => {};
13954
+ /**
13955
+ * Flush all queued requests to the database
13956
+ */
13957
+ async function flush() {
13958
+ if (flushing || queue.length === 0) return;
13959
+ flushing = true;
13960
+ const batch = queue;
13961
+ queue = [];
13962
+ try {
13963
+ log(`[BatchWriter] Flushing ${batch.length} requests`);
13964
+ log(`[BatchWriter] Flushed ${(await deps.batchInsertRequests(batch)).count} requests successfully`);
13965
+ } catch (error$45) {
13966
+ const errorMsg = error$45 instanceof Error ? error$45.message : String(error$45);
13967
+ logger.error(`[BatchWriter] Flush failed, re-queuing requests: ${errorMsg}`);
13968
+ queue = [...batch, ...queue];
13969
+ } finally {
13970
+ flushing = false;
13971
+ }
13972
+ }
13973
+ /**
13974
+ * Start the periodic flush timer
13975
+ */
13976
+ function start() {
13977
+ if (running) return;
13978
+ running = true;
13979
+ flushTimer = setInterval(() => {
13980
+ flush().catch((err) => {
13981
+ const errorMsg = err instanceof Error ? err.message : String(err);
13982
+ logger.error(`[BatchWriter] Periodic flush error: ${errorMsg}`);
13983
+ });
13984
+ }, flushIntervalMs);
13985
+ log(`[BatchWriter] Started with ${flushIntervalMs}ms flush interval`);
13986
+ }
13987
+ /**
13988
+ * Stop the batch writer and flush remaining items
13989
+ */
13990
+ async function stop() {
13991
+ if (!running) return;
13992
+ running = false;
13993
+ if (flushTimer) {
13994
+ clearInterval(flushTimer);
13995
+ flushTimer = null;
13996
+ }
13997
+ await flush();
13998
+ log("[BatchWriter] Stopped");
13999
+ }
14000
+ /**
14001
+ * Add a request to the batch queue
14002
+ */
14003
+ function enqueue(request) {
14004
+ queue.push(request);
14005
+ log(`[BatchWriter] Enqueued request ${request.requestId}, queue size: ${queue.length}`);
14006
+ if (!running) start();
14007
+ if (queue.length >= maxBatchSize) {
14008
+ log(`[BatchWriter] Max batch size reached, forcing flush`);
14009
+ flush().catch((err) => {
14010
+ const errorMsg = err instanceof Error ? err.message : String(err);
14011
+ logger.error(`[BatchWriter] Forced flush error: ${errorMsg}`);
14012
+ });
14013
+ }
14014
+ }
14015
+ return {
14016
+ enqueue,
14017
+ flush,
14018
+ stop,
14019
+ queueLength: () => queue.length,
14020
+ isRunning: () => running
14021
+ };
14022
+ }
14023
+ /**
14024
+ * Global singleton instance
14025
+ * Lazily initialized when first accessed
14026
+ */
14027
+ let globalWriter = null;
14028
+ /**
14029
+ * Get or create the global BatchWriter instance
14030
+ *
14031
+ * @param deps - Database dependencies (required on first call)
14032
+ * @param config - Optional configuration
14033
+ * @returns The global BatchWriter instance
14034
+ */
14035
+ function getGlobalBatchWriter(deps, config$1) {
14036
+ if (!globalWriter) {
14037
+ if (!deps) throw new Error("BatchWriter dependencies required on first initialization");
14038
+ globalWriter = createBatchWriter(deps, config$1);
14039
+ }
14040
+ return globalWriter;
14041
+ }
14042
+
14043
+ //#endregion
14044
+ //#region src/server/middlewares/costTracking.ts
14045
+ /**
14046
+ * Calculate cost in micro-dollars
14047
+ * 1 dollar = 1,000,000 micro-dollars
14048
+ */
14049
+ function calculateCost(usage, pricing) {
14050
+ const inputCost = Math.round(usage.promptTokens * pricing.inputCostPer1M);
14051
+ const outputCost = Math.round(usage.completionTokens * pricing.outputCostPer1M);
14052
+ return {
14053
+ inputCost,
14054
+ outputCost,
14055
+ totalCost: inputCost + outputCost
14056
+ };
14057
+ }
14058
+ /**
14059
+ * Simple pricing provider that fetches from models.dev
14060
+ */
14061
+ var PricingProvider = class {
14062
+ cache = /* @__PURE__ */ new Map();
14063
+ lastFetch = 0;
14064
+ cacheTTL = 300 * 1e3;
14065
+ fetchPromise = null;
14066
+ getCacheKey(provider, model) {
14067
+ return `${provider.toLowerCase()}:${model.toLowerCase()}`;
14068
+ }
14069
+ async fetchPricingData() {
14070
+ try {
14071
+ const response = await fetch("https://models.dev/api.json");
14072
+ if (!response.ok) return;
14073
+ const data = await response.json();
14074
+ this.cache.clear();
14075
+ for (const [providerId, provider] of Object.entries(data)) {
14076
+ const p = provider;
14077
+ if (!p.models) continue;
14078
+ for (const [, model] of Object.entries(p.models)) {
14079
+ if (!model.cost) continue;
14080
+ const cacheKey = this.getCacheKey(providerId, model.id);
14081
+ this.cache.set(cacheKey, {
14082
+ inputCostPer1M: model.cost.input ?? 0,
14083
+ outputCostPer1M: model.cost.output ?? 0
14084
+ });
14085
+ }
14086
+ }
14087
+ this.lastFetch = Date.now();
14088
+ } catch {}
14089
+ }
14090
+ async ensureFreshCache() {
14091
+ if (Date.now() - this.lastFetch < this.cacheTTL && this.cache.size > 0) return;
14092
+ if (!this.fetchPromise) this.fetchPromise = this.fetchPricingData().finally(() => {
14093
+ this.fetchPromise = null;
14094
+ });
14095
+ await this.fetchPromise;
14096
+ }
14097
+ async getModelPricing(provider, model) {
14098
+ await this.ensureFreshCache();
14099
+ return this.cache.get(this.getCacheKey(provider, model)) || null;
14100
+ }
14101
+ };
14102
+ const pricingProvider = new PricingProvider();
14103
+ /**
14104
+ * Creates cost tracking middleware that logs LLM requests with usage and cost data.
14105
+ *
14106
+ * Features:
14107
+ * - Tracks both streaming and non-streaming requests
14108
+ * - Calculates costs using models.dev pricing data
14109
+ * - Batches database writes for performance
14110
+ * - Adds x-llmops-request-id header for tracing
14111
+ */
14112
+ function createCostTrackingMiddleware(config$1 = {}) {
14113
+ const { enabled = true, trackErrors = true, flushIntervalMs = 2e3, debug = false } = config$1;
14114
+ const log = debug ? (msg) => logger.debug(`[CostTracking] ${msg}`) : () => {};
14115
+ return async (c, next) => {
14116
+ if (!enabled) return next();
14117
+ const path = c.req.path;
14118
+ if (!path.endsWith("/chat/completions") && !path.endsWith("/completions")) return next();
14119
+ const requestId = randomUUID();
14120
+ const startTime = Date.now();
14121
+ c.header("x-llmops-request-id", requestId);
14122
+ let body = {};
14123
+ let isStreaming = false;
14124
+ try {
14125
+ body = await c.req.raw.clone().json();
14126
+ isStreaming = body.stream === true;
14127
+ if (isStreaming) {
14128
+ body = ensureStreamUsageEnabled(body);
14129
+ const newHeaders = new Headers(c.req.raw.headers);
14130
+ const newRequest = new Request(c.req.raw.url, {
14131
+ method: c.req.raw.method,
14132
+ headers: newHeaders,
14133
+ body: JSON.stringify(body),
14134
+ duplex: "half"
14135
+ });
14136
+ Object.defineProperty(c.req, "raw", {
14137
+ value: newRequest,
14138
+ writable: true,
14139
+ configurable: true
14140
+ });
14141
+ c.req.bodyCache = {};
14142
+ }
14143
+ } catch {
14144
+ log("Failed to parse request body");
14145
+ }
14146
+ const context = {
14147
+ requestId,
14148
+ startTime,
14149
+ provider: "",
14150
+ model: body.model || "",
14151
+ configId: c.get("configId"),
14152
+ endpoint: path,
14153
+ isStreaming
14154
+ };
14155
+ c.set("__costTrackingContext", context);
14156
+ await next();
14157
+ const response = c.res;
14158
+ const statusCode = response.status;
14159
+ const latencyMs = Date.now() - startTime;
14160
+ const variantModel = c.get("variantModel") || context.model;
14161
+ let provider = "unknown";
14162
+ const llmopsConfigHeader = c.req.header("x-llmops-config");
14163
+ if (llmopsConfigHeader) try {
14164
+ provider = JSON.parse(llmopsConfigHeader).provider || provider;
14165
+ } catch {}
14166
+ if (!variantModel) {
14167
+ log(`Skipping request tracking - no model info`);
14168
+ return;
14169
+ }
14170
+ const db = c.get("db");
14171
+ const batchWriter = getGlobalBatchWriter({ batchInsertRequests: (requests) => db.batchInsertRequests(requests) }, {
14172
+ flushIntervalMs,
14173
+ debug
14174
+ });
14175
+ if (isStreaming && response.body) {
14176
+ const { response: wrappedResponse, usagePromise } = wrapStreamingResponse(response);
14177
+ c.res = wrappedResponse;
14178
+ usagePromise.then(async (usage) => {
14179
+ await processUsageAndLog({
14180
+ requestId,
14181
+ provider,
14182
+ model: variantModel,
14183
+ configId: c.get("configId"),
14184
+ variantId: c.get("variantId"),
14185
+ endpoint: context.endpoint,
14186
+ statusCode,
14187
+ latencyMs,
14188
+ isStreaming: true,
14189
+ usage: usage ? {
14190
+ promptTokens: usage.promptTokens,
14191
+ completionTokens: usage.completionTokens,
14192
+ totalTokens: usage.totalTokens,
14193
+ cachedTokens: usage.cachedTokens
14194
+ } : null,
14195
+ batchWriter,
14196
+ trackErrors,
14197
+ log
14198
+ });
14199
+ }).catch((err) => {
14200
+ logger.error(`[CostTracking] Failed to process streaming usage: ${err}`);
14201
+ });
14202
+ } else {
14203
+ let usage = null;
14204
+ try {
14205
+ const responseBody = await response.clone().json();
14206
+ if (responseBody.usage) usage = {
14207
+ promptTokens: responseBody.usage.prompt_tokens || 0,
14208
+ completionTokens: responseBody.usage.completion_tokens || 0,
14209
+ totalTokens: responseBody.usage.total_tokens || 0,
14210
+ cachedTokens: responseBody.usage.prompt_tokens_details?.cached_tokens
14211
+ };
14212
+ } catch {
14213
+ log("Failed to parse response body for usage");
14214
+ }
14215
+ await processUsageAndLog({
14216
+ requestId,
14217
+ provider,
14218
+ model: variantModel,
14219
+ configId: c.get("configId"),
14220
+ variantId: c.get("variantId"),
14221
+ endpoint: context.endpoint,
14222
+ statusCode,
14223
+ latencyMs,
14224
+ isStreaming: false,
14225
+ usage,
14226
+ batchWriter,
14227
+ trackErrors,
14228
+ log
14229
+ });
14230
+ }
14231
+ };
14232
+ }
14233
+ /**
14234
+ * Process usage data and log to batch writer
14235
+ */
14236
+ async function processUsageAndLog(params) {
14237
+ const { requestId, provider, model, configId, variantId, endpoint, statusCode, latencyMs, isStreaming, usage, batchWriter, trackErrors, log } = params;
14238
+ if (!trackErrors && statusCode >= 400) {
14239
+ log(`Skipping error response (${statusCode})`);
14240
+ return;
14241
+ }
14242
+ let cost = 0;
14243
+ let inputCost = 0;
14244
+ let outputCost = 0;
14245
+ if (usage && usage.promptTokens + usage.completionTokens > 0) try {
14246
+ const pricing = await pricingProvider.getModelPricing(provider, model);
14247
+ if (pricing) {
14248
+ const costResult = calculateCost({
14249
+ promptTokens: usage.promptTokens,
14250
+ completionTokens: usage.completionTokens
14251
+ }, pricing);
14252
+ cost = costResult.totalCost;
14253
+ inputCost = costResult.inputCost;
14254
+ outputCost = costResult.outputCost;
14255
+ log(`Calculated cost: ${cost} micro-dollars for ${provider}/${model}`);
14256
+ } else log(`No pricing found for ${provider}/${model}`);
14257
+ } catch (err) {
14258
+ logger.error(`[CostTracking] Failed to calculate cost: ${err}`);
14259
+ }
14260
+ const requestData = {
14261
+ requestId,
14262
+ configId: configId || null,
14263
+ variantId: variantId || null,
14264
+ provider,
14265
+ model,
14266
+ promptTokens: usage?.promptTokens || 0,
14267
+ completionTokens: usage?.completionTokens || 0,
14268
+ totalTokens: usage?.totalTokens || 0,
14269
+ cachedTokens: usage?.cachedTokens || 0,
14270
+ cost,
14271
+ inputCost,
14272
+ outputCost,
14273
+ endpoint,
14274
+ statusCode,
14275
+ latencyMs,
14276
+ isStreaming,
14277
+ tags: {}
14278
+ };
14279
+ batchWriter.enqueue(requestData);
14280
+ log(`Enqueued request ${requestId} for logging`);
14281
+ }
14282
+
13545
14283
  //#endregion
13546
14284
  //#region src/server/handlers/genai/index.ts
13547
14285
  const app$2 = new Hono();
13548
14286
  app$2.use("*", prettyJSON()).get("/health", async (c) => {
13549
14287
  return c.json({ status: "healthy" });
13550
- }).use("*", requestValidator).use("*", createRequestGuardMiddleware()).use("*", createGatewayAdapterMiddleware()).route("/", gateway).notFound((c) => c.json({ error: {
14288
+ }).use("*", requestValidator).use("*", createRequestGuardMiddleware()).use("*", createCostTrackingMiddleware()).use("*", createGatewayAdapterMiddleware()).route("/", gateway).notFound((c) => c.json({ error: {
13551
14289
  message: "Not Found",
13552
14290
  type: "invalid_request_error"
13553
14291
  } }, 404)).onError((err, c) => {
@@ -13578,10 +14316,12 @@ app.use("/assets/*", serveStatic({
13578
14316
  if (!c.req.path.startsWith("/api")) {
13579
14317
  const basePath = c.var.llmopsConfig?.basePath || "";
13580
14318
  const llmProviders = c.var.llmProviders || [];
14319
+ const authType = c.var.llmopsConfig?.auth?.type || "basic";
13581
14320
  return c.html(renderer({
13582
14321
  basePath,
13583
14322
  dev: env.LLMOPS_DEV === "true",
13584
- llmProviders
14323
+ llmProviders,
14324
+ authType
13585
14325
  }));
13586
14326
  }
13587
14327
  await next();