@llmops/core 0.1.5-beta.1 → 0.1.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.
package/dist/index.cjs CHANGED
@@ -1,6 +1,7 @@
1
- const require_db = require('./db-C9-M-kdS.cjs');
1
+ const require_db = require('./db-eEfIe5dO.cjs');
2
2
  let __llmops_gateway = require("@llmops/gateway");
3
3
  __llmops_gateway = require_db.__toESM(__llmops_gateway);
4
+ let kysely = require("kysely");
4
5
  let node_fs_promises = require("node:fs/promises");
5
6
  node_fs_promises = require_db.__toESM(node_fs_promises);
6
7
  let node_path = require("node:path");
@@ -1282,7 +1283,11 @@ const createConfigVariantDataLayer = (db) => {
1282
1283
  "version"
1283
1284
  ]).where("variantId", "=", configVariant.variantId).orderBy("version", "desc").limit(1).executeTakeFirst();
1284
1285
  if (!versionData) throw new LLMOpsError(`No variant version found for variant ${configVariant.variantId}`);
1285
- return versionData;
1286
+ return {
1287
+ ...versionData,
1288
+ configId: resolvedConfigId,
1289
+ variantId: configVariant.variantId
1290
+ };
1286
1291
  }
1287
1292
  };
1288
1293
  };
@@ -1442,6 +1447,287 @@ const createEnvironmentSecretDataLayer = (db) => {
1442
1447
  };
1443
1448
  };
1444
1449
 
1450
+ //#endregion
1451
+ //#region src/datalayer/llmRequests.ts
1452
+ /**
1453
+ * Schema for inserting a new LLM request log
1454
+ */
1455
+ const insertLLMRequestSchema = require_db.zod_default.object({
1456
+ requestId: require_db.zod_default.string().uuid(),
1457
+ configId: require_db.zod_default.string().uuid().nullable().optional(),
1458
+ variantId: require_db.zod_default.string().uuid().nullable().optional(),
1459
+ provider: require_db.zod_default.string(),
1460
+ model: require_db.zod_default.string(),
1461
+ promptTokens: require_db.zod_default.number().int().default(0),
1462
+ completionTokens: require_db.zod_default.number().int().default(0),
1463
+ totalTokens: require_db.zod_default.number().int().default(0),
1464
+ cachedTokens: require_db.zod_default.number().int().default(0),
1465
+ cost: require_db.zod_default.number().int().default(0),
1466
+ inputCost: require_db.zod_default.number().int().default(0),
1467
+ outputCost: require_db.zod_default.number().int().default(0),
1468
+ endpoint: require_db.zod_default.string(),
1469
+ statusCode: require_db.zod_default.number().int(),
1470
+ latencyMs: require_db.zod_default.number().int().default(0),
1471
+ isStreaming: require_db.zod_default.boolean().default(false),
1472
+ userId: require_db.zod_default.string().nullable().optional(),
1473
+ tags: require_db.zod_default.record(require_db.zod_default.string(), require_db.zod_default.string()).default({})
1474
+ });
1475
+ /**
1476
+ * Schema for listing LLM requests
1477
+ */
1478
+ const listRequestsSchema = require_db.zod_default.object({
1479
+ limit: require_db.zod_default.number().int().positive().max(1e3).default(100),
1480
+ offset: require_db.zod_default.number().int().nonnegative().default(0),
1481
+ configId: require_db.zod_default.string().uuid().optional(),
1482
+ provider: require_db.zod_default.string().optional(),
1483
+ model: require_db.zod_default.string().optional(),
1484
+ startDate: require_db.zod_default.date().optional(),
1485
+ endDate: require_db.zod_default.date().optional()
1486
+ });
1487
+ /**
1488
+ * Schema for date range queries
1489
+ */
1490
+ const dateRangeSchema = require_db.zod_default.object({
1491
+ startDate: require_db.zod_default.date(),
1492
+ endDate: require_db.zod_default.date()
1493
+ });
1494
+ /**
1495
+ * Schema for cost summary with grouping
1496
+ */
1497
+ const costSummarySchema = require_db.zod_default.object({
1498
+ startDate: require_db.zod_default.date(),
1499
+ endDate: require_db.zod_default.date(),
1500
+ groupBy: require_db.zod_default.enum([
1501
+ "day",
1502
+ "hour",
1503
+ "model",
1504
+ "provider",
1505
+ "config"
1506
+ ]).optional()
1507
+ });
1508
+ /**
1509
+ * Helper to create column reference for SQL
1510
+ * Uses sql.ref() to properly quote column names for the database
1511
+ */
1512
+ const col = (name) => kysely.sql.ref(name);
1513
+ const tableCol = (table, name) => kysely.sql.ref(`${table}.${name}`);
1514
+ const createLLMRequestsDataLayer = (db) => {
1515
+ return {
1516
+ batchInsertRequests: async (requests) => {
1517
+ if (requests.length === 0) return { count: 0 };
1518
+ const validatedRequests = await Promise.all(requests.map(async (req) => {
1519
+ const result = await insertLLMRequestSchema.safeParseAsync(req);
1520
+ if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
1521
+ return result.data;
1522
+ }));
1523
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1524
+ const values = validatedRequests.map((req) => ({
1525
+ id: (0, node_crypto.randomUUID)(),
1526
+ requestId: req.requestId,
1527
+ configId: req.configId ?? null,
1528
+ variantId: req.variantId ?? null,
1529
+ provider: req.provider,
1530
+ model: req.model,
1531
+ promptTokens: req.promptTokens,
1532
+ completionTokens: req.completionTokens,
1533
+ totalTokens: req.totalTokens,
1534
+ cachedTokens: req.cachedTokens,
1535
+ cost: req.cost,
1536
+ inputCost: req.inputCost,
1537
+ outputCost: req.outputCost,
1538
+ endpoint: req.endpoint,
1539
+ statusCode: req.statusCode,
1540
+ latencyMs: req.latencyMs,
1541
+ isStreaming: req.isStreaming,
1542
+ userId: req.userId ?? null,
1543
+ tags: JSON.stringify(req.tags),
1544
+ createdAt: now,
1545
+ updatedAt: now
1546
+ }));
1547
+ await db.insertInto("llm_requests").values(values).execute();
1548
+ return { count: values.length };
1549
+ },
1550
+ insertRequest: async (request) => {
1551
+ const result = await insertLLMRequestSchema.safeParseAsync(request);
1552
+ if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
1553
+ const req = result.data;
1554
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1555
+ return db.insertInto("llm_requests").values({
1556
+ id: (0, node_crypto.randomUUID)(),
1557
+ requestId: req.requestId,
1558
+ configId: req.configId ?? null,
1559
+ variantId: req.variantId ?? null,
1560
+ provider: req.provider,
1561
+ model: req.model,
1562
+ promptTokens: req.promptTokens,
1563
+ completionTokens: req.completionTokens,
1564
+ totalTokens: req.totalTokens,
1565
+ cachedTokens: req.cachedTokens,
1566
+ cost: req.cost,
1567
+ inputCost: req.inputCost,
1568
+ outputCost: req.outputCost,
1569
+ endpoint: req.endpoint,
1570
+ statusCode: req.statusCode,
1571
+ latencyMs: req.latencyMs,
1572
+ isStreaming: req.isStreaming,
1573
+ userId: req.userId ?? null,
1574
+ tags: JSON.stringify(req.tags),
1575
+ createdAt: now,
1576
+ updatedAt: now
1577
+ }).returningAll().executeTakeFirst();
1578
+ },
1579
+ listRequests: async (params) => {
1580
+ const result = await listRequestsSchema.safeParseAsync(params || {});
1581
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1582
+ const { limit, offset, configId, provider, model, startDate, endDate } = result.data;
1583
+ let baseQuery = db.selectFrom("llm_requests");
1584
+ if (configId) baseQuery = baseQuery.where("configId", "=", configId);
1585
+ if (provider) baseQuery = baseQuery.where("provider", "=", provider);
1586
+ if (model) baseQuery = baseQuery.where("model", "=", model);
1587
+ if (startDate) baseQuery = baseQuery.where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`);
1588
+ if (endDate) baseQuery = baseQuery.where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`);
1589
+ const countResult = await baseQuery.select(kysely.sql`COUNT(*)`.as("total")).executeTakeFirst();
1590
+ const total = Number(countResult?.total ?? 0);
1591
+ return {
1592
+ data: await baseQuery.selectAll().orderBy("createdAt", "desc").limit(limit).offset(offset).execute(),
1593
+ total,
1594
+ limit,
1595
+ offset
1596
+ };
1597
+ },
1598
+ getRequestByRequestId: async (requestId) => {
1599
+ return db.selectFrom("llm_requests").selectAll().where("requestId", "=", requestId).executeTakeFirst();
1600
+ },
1601
+ getTotalCost: async (params) => {
1602
+ const result = await dateRangeSchema.safeParseAsync(params);
1603
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1604
+ const { startDate, endDate } = result.data;
1605
+ return await db.selectFrom("llm_requests").select([
1606
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1607
+ kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1608
+ kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1609
+ kysely.sql`COALESCE(SUM(${col("promptTokens")}), 0)`.as("totalPromptTokens"),
1610
+ kysely.sql`COALESCE(SUM(${col("completionTokens")}), 0)`.as("totalCompletionTokens"),
1611
+ kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1612
+ kysely.sql`COUNT(*)`.as("requestCount")
1613
+ ]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
1614
+ },
1615
+ getCostByModel: async (params) => {
1616
+ const result = await dateRangeSchema.safeParseAsync(params);
1617
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1618
+ const { startDate, endDate } = result.data;
1619
+ return db.selectFrom("llm_requests").select([
1620
+ "provider",
1621
+ "model",
1622
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1623
+ kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1624
+ kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1625
+ kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1626
+ kysely.sql`COUNT(*)`.as("requestCount"),
1627
+ kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
1628
+ ]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(["provider", "model"]).orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
1629
+ },
1630
+ getCostByProvider: async (params) => {
1631
+ const result = await dateRangeSchema.safeParseAsync(params);
1632
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1633
+ const { startDate, endDate } = result.data;
1634
+ return db.selectFrom("llm_requests").select([
1635
+ "provider",
1636
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1637
+ kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1638
+ kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1639
+ kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1640
+ kysely.sql`COUNT(*)`.as("requestCount"),
1641
+ kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
1642
+ ]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy("provider").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
1643
+ },
1644
+ getCostByConfig: async (params) => {
1645
+ const result = await dateRangeSchema.safeParseAsync(params);
1646
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1647
+ const { startDate, endDate } = result.data;
1648
+ return db.selectFrom("llm_requests").leftJoin("configs", "llm_requests.configId", "configs.id").select([
1649
+ "llm_requests.configId",
1650
+ "configs.name as configName",
1651
+ "configs.slug as configSlug",
1652
+ kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "cost")}), 0)`.as("totalCost"),
1653
+ kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "inputCost")}), 0)`.as("totalInputCost"),
1654
+ kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "outputCost")}), 0)`.as("totalOutputCost"),
1655
+ kysely.sql`COALESCE(SUM(${tableCol("llm_requests", "totalTokens")}), 0)`.as("totalTokens"),
1656
+ kysely.sql`COUNT(*)`.as("requestCount")
1657
+ ]).where(kysely.sql`${tableCol("llm_requests", "createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${tableCol("llm_requests", "createdAt")} <= ${endDate.toISOString()}`).groupBy([
1658
+ "llm_requests.configId",
1659
+ "configs.name",
1660
+ "configs.slug"
1661
+ ]).orderBy(kysely.sql`SUM(${tableCol("llm_requests", "cost")})`, "desc").execute();
1662
+ },
1663
+ getDailyCosts: async (params) => {
1664
+ const result = await dateRangeSchema.safeParseAsync(params);
1665
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1666
+ const { startDate, endDate } = result.data;
1667
+ return db.selectFrom("llm_requests").select([
1668
+ kysely.sql`DATE(${col("createdAt")})`.as("date"),
1669
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1670
+ kysely.sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1671
+ kysely.sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1672
+ kysely.sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1673
+ kysely.sql`COUNT(*)`.as("requestCount")
1674
+ ]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(kysely.sql`DATE(${col("createdAt")})`).orderBy(kysely.sql`DATE(${col("createdAt")})`, "asc").execute();
1675
+ },
1676
+ getCostSummary: async (params) => {
1677
+ const result = await costSummarySchema.safeParseAsync(params);
1678
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1679
+ const { startDate, endDate, groupBy } = result.data;
1680
+ const baseQuery = db.selectFrom("llm_requests").where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`);
1681
+ switch (groupBy) {
1682
+ case "day": return baseQuery.select([
1683
+ kysely.sql`DATE(${col("createdAt")})`.as("groupKey"),
1684
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1685
+ kysely.sql`COUNT(*)`.as("requestCount")
1686
+ ]).groupBy(kysely.sql`DATE(${col("createdAt")})`).orderBy(kysely.sql`DATE(${col("createdAt")})`, "asc").execute();
1687
+ case "hour": return baseQuery.select([
1688
+ kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`.as("groupKey"),
1689
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1690
+ kysely.sql`COUNT(*)`.as("requestCount")
1691
+ ]).groupBy(kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`).orderBy(kysely.sql`DATE_TRUNC('hour', ${col("createdAt")})`, "asc").execute();
1692
+ case "model": return baseQuery.select([
1693
+ kysely.sql`${col("provider")} || '/' || ${col("model")}`.as("groupKey"),
1694
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1695
+ kysely.sql`COUNT(*)`.as("requestCount")
1696
+ ]).groupBy(["provider", "model"]).orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
1697
+ case "provider": return baseQuery.select([
1698
+ kysely.sql`${col("provider")}`.as("groupKey"),
1699
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1700
+ kysely.sql`COUNT(*)`.as("requestCount")
1701
+ ]).groupBy("provider").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
1702
+ case "config": return baseQuery.select([
1703
+ kysely.sql`COALESCE(${col("configId")}::text, 'no-config')`.as("groupKey"),
1704
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1705
+ kysely.sql`COUNT(*)`.as("requestCount")
1706
+ ]).groupBy("configId").orderBy(kysely.sql`SUM(${col("cost")})`, "desc").execute();
1707
+ default: return baseQuery.select([
1708
+ kysely.sql`'total'`.as("groupKey"),
1709
+ kysely.sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1710
+ kysely.sql`COUNT(*)`.as("requestCount")
1711
+ ]).execute();
1712
+ }
1713
+ },
1714
+ getRequestStats: async (params) => {
1715
+ const result = await dateRangeSchema.safeParseAsync(params);
1716
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1717
+ const { startDate, endDate } = result.data;
1718
+ return await db.selectFrom("llm_requests").select([
1719
+ kysely.sql`COUNT(*)`.as("totalRequests"),
1720
+ kysely.sql`COUNT(CASE WHEN ${col("statusCode")} >= 200 AND ${col("statusCode")} < 300 THEN 1 END)`.as("successfulRequests"),
1721
+ kysely.sql`COUNT(CASE WHEN ${col("statusCode")} >= 400 THEN 1 END)`.as("failedRequests"),
1722
+ kysely.sql`COUNT(CASE WHEN ${col("isStreaming")} = true THEN 1 END)`.as("streamingRequests"),
1723
+ kysely.sql`AVG(${col("latencyMs")})`.as("avgLatencyMs"),
1724
+ kysely.sql`MAX(${col("latencyMs")})`.as("maxLatencyMs"),
1725
+ kysely.sql`MIN(${col("latencyMs")})`.as("minLatencyMs")
1726
+ ]).where(kysely.sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(kysely.sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
1727
+ }
1728
+ };
1729
+ };
1730
+
1445
1731
  //#endregion
1446
1732
  //#region src/datalayer/targetingRules.ts
1447
1733
  const createTargetingRule = require_db.zod_default.object({
@@ -1868,6 +2154,7 @@ const createDataLayer = async (db) => {
1868
2154
  ...createConfigVariantDataLayer(db),
1869
2155
  ...createEnvironmentDataLayer(db),
1870
2156
  ...createEnvironmentSecretDataLayer(db),
2157
+ ...createLLMRequestsDataLayer(db),
1871
2158
  ...createTargetingRulesDataLayer(db),
1872
2159
  ...createVariantDataLayer(db),
1873
2160
  ...createVariantVersionsDataLayer(db),
@@ -1875,22 +2162,230 @@ const createDataLayer = async (db) => {
1875
2162
  };
1876
2163
  };
1877
2164
 
2165
+ //#endregion
2166
+ //#region src/pricing/calculator.ts
2167
+ /**
2168
+ * Calculate the cost of an LLM request in micro-dollars
2169
+ *
2170
+ * Micro-dollars are used to avoid floating-point precision issues:
2171
+ * - 1 dollar = 1,000,000 micro-dollars
2172
+ * - $0.001 = 1,000 micro-dollars
2173
+ * - $0.000001 = 1 micro-dollar
2174
+ *
2175
+ * @param usage - Token usage data from the LLM response
2176
+ * @param pricing - Model pricing information
2177
+ * @returns Cost breakdown in micro-dollars
2178
+ *
2179
+ * @example
2180
+ * ```typescript
2181
+ * const usage = { promptTokens: 1000, completionTokens: 500 };
2182
+ * const pricing = { inputCostPer1M: 2.5, outputCostPer1M: 10.0 };
2183
+ * const cost = calculateCost(usage, pricing);
2184
+ * // cost = { inputCost: 2500, outputCost: 5000, totalCost: 7500 }
2185
+ * // In dollars: $0.0025 input + $0.005 output = $0.0075 total
2186
+ * ```
2187
+ */
2188
+ function calculateCost(usage, pricing) {
2189
+ const inputCost = Math.round(usage.promptTokens * pricing.inputCostPer1M);
2190
+ const outputCost = Math.round(usage.completionTokens * pricing.outputCostPer1M);
2191
+ return {
2192
+ inputCost,
2193
+ outputCost,
2194
+ totalCost: inputCost + outputCost
2195
+ };
2196
+ }
2197
+ /**
2198
+ * Convert micro-dollars to dollars
2199
+ *
2200
+ * @param microDollars - Amount in micro-dollars
2201
+ * @returns Amount in dollars
2202
+ *
2203
+ * @example
2204
+ * ```typescript
2205
+ * microDollarsToDollars(7500); // 0.0075
2206
+ * microDollarsToDollars(1000000); // 1.0
2207
+ * ```
2208
+ */
2209
+ function microDollarsToDollars(microDollars) {
2210
+ return microDollars / 1e6;
2211
+ }
2212
+ /**
2213
+ * Convert dollars to micro-dollars
2214
+ *
2215
+ * @param dollars - Amount in dollars
2216
+ * @returns Amount in micro-dollars (rounded to nearest integer)
2217
+ *
2218
+ * @example
2219
+ * ```typescript
2220
+ * dollarsToMicroDollars(0.0075); // 7500
2221
+ * dollarsToMicroDollars(1.0); // 1000000
2222
+ * ```
2223
+ */
2224
+ function dollarsToMicroDollars(dollars) {
2225
+ return Math.round(dollars * 1e6);
2226
+ }
2227
+ /**
2228
+ * Format micro-dollars as a human-readable dollar string
2229
+ *
2230
+ * @param microDollars - Amount in micro-dollars
2231
+ * @param decimals - Number of decimal places (default: 6)
2232
+ * @returns Formatted dollar string
2233
+ *
2234
+ * @example
2235
+ * ```typescript
2236
+ * formatCost(7500); // "$0.007500"
2237
+ * formatCost(1234567, 2); // "$1.23"
2238
+ * ```
2239
+ */
2240
+ function formatCost(microDollars, decimals = 6) {
2241
+ return `$${microDollarsToDollars(microDollars).toFixed(decimals)}`;
2242
+ }
2243
+
2244
+ //#endregion
2245
+ //#region src/pricing/provider.ts
2246
+ const MODELS_DEV_API = "https://models.dev/api.json";
2247
+ /**
2248
+ * Pricing provider that fetches data from models.dev API
2249
+ *
2250
+ * Features:
2251
+ * - Caches pricing data with configurable TTL (default 5 minutes)
2252
+ * - Supports fallback to local cache on fetch failure
2253
+ * - Thread-safe cache refresh
2254
+ */
2255
+ var ModelsDevPricingProvider = class {
2256
+ cache = /* @__PURE__ */ new Map();
2257
+ lastFetch = 0;
2258
+ cacheTTL;
2259
+ fetchPromise = null;
2260
+ ready = false;
2261
+ /**
2262
+ * Create a new ModelsDevPricingProvider
2263
+ *
2264
+ * @param cacheTTL - Cache TTL in milliseconds (default: 5 minutes)
2265
+ */
2266
+ constructor(cacheTTL = 300 * 1e3) {
2267
+ this.cacheTTL = cacheTTL;
2268
+ }
2269
+ /**
2270
+ * Generate a cache key for a provider/model combination
2271
+ */
2272
+ getCacheKey(provider, model) {
2273
+ return `${provider.toLowerCase()}:${model.toLowerCase()}`;
2274
+ }
2275
+ /**
2276
+ * Fetch pricing data from models.dev API
2277
+ */
2278
+ async fetchPricingData() {
2279
+ try {
2280
+ require_db.logger.debug("[Pricing] Fetching pricing data from models.dev");
2281
+ const response = await fetch(MODELS_DEV_API);
2282
+ if (!response.ok) throw new Error(`Failed to fetch models.dev API: ${response.status}`);
2283
+ const data = await response.json();
2284
+ this.cache.clear();
2285
+ for (const [providerId, provider] of Object.entries(data)) {
2286
+ if (!provider.models) continue;
2287
+ for (const [_modelId, model] of Object.entries(provider.models)) {
2288
+ if (!model.cost) continue;
2289
+ const cacheKey = this.getCacheKey(providerId, model.id);
2290
+ this.cache.set(cacheKey, {
2291
+ inputCostPer1M: model.cost.input ?? 0,
2292
+ outputCostPer1M: model.cost.output ?? 0,
2293
+ cacheReadCostPer1M: model.cost.cache_read,
2294
+ cacheWriteCostPer1M: model.cost.cache_write,
2295
+ reasoningCostPer1M: model.cost.reasoning
2296
+ });
2297
+ const nameKey = this.getCacheKey(providerId, model.name);
2298
+ if (nameKey !== cacheKey) this.cache.set(nameKey, this.cache.get(cacheKey));
2299
+ }
2300
+ }
2301
+ this.lastFetch = Date.now();
2302
+ this.ready = true;
2303
+ require_db.logger.debug(`[Pricing] Cached pricing for ${this.cache.size} models from models.dev`);
2304
+ } catch (error) {
2305
+ require_db.logger.error(`[Pricing] Failed to fetch pricing data: ${error instanceof Error ? error.message : String(error)}`);
2306
+ if (this.cache.size === 0) throw error;
2307
+ }
2308
+ }
2309
+ /**
2310
+ * Ensure cache is fresh, fetching if necessary
2311
+ */
2312
+ async ensureFreshCache() {
2313
+ if (!(Date.now() - this.lastFetch > this.cacheTTL) && this.cache.size > 0) return;
2314
+ if (!this.fetchPromise) this.fetchPromise = this.fetchPricingData().finally(() => {
2315
+ this.fetchPromise = null;
2316
+ });
2317
+ await this.fetchPromise;
2318
+ }
2319
+ /**
2320
+ * Get pricing for a specific model
2321
+ */
2322
+ async getModelPricing(provider, model) {
2323
+ await this.ensureFreshCache();
2324
+ const cacheKey = this.getCacheKey(provider, model);
2325
+ const pricing = this.cache.get(cacheKey);
2326
+ if (!pricing) {
2327
+ require_db.logger.debug(`[Pricing] No pricing found for ${provider}/${model}, trying partial match`);
2328
+ for (const [key, value] of this.cache.entries()) if (key.startsWith(`${provider.toLowerCase()}:`)) {
2329
+ const modelPart = key.split(":")[1];
2330
+ if (model.toLowerCase().includes(modelPart)) {
2331
+ require_db.logger.debug(`[Pricing] Found partial match: ${key}`);
2332
+ return value;
2333
+ }
2334
+ }
2335
+ return null;
2336
+ }
2337
+ return pricing;
2338
+ }
2339
+ /**
2340
+ * Force refresh the pricing cache
2341
+ */
2342
+ async refreshCache() {
2343
+ this.lastFetch = 0;
2344
+ await this.ensureFreshCache();
2345
+ }
2346
+ /**
2347
+ * Check if the provider is ready
2348
+ */
2349
+ isReady() {
2350
+ return this.ready;
2351
+ }
2352
+ /**
2353
+ * Get the number of cached models (for debugging)
2354
+ */
2355
+ getCacheSize() {
2356
+ return this.cache.size;
2357
+ }
2358
+ };
2359
+ let defaultProvider = null;
2360
+ /**
2361
+ * Get the default pricing provider instance
2362
+ */
2363
+ function getDefaultPricingProvider() {
2364
+ if (!defaultProvider) defaultProvider = new ModelsDevPricingProvider();
2365
+ return defaultProvider;
2366
+ }
2367
+
1878
2368
  //#endregion
1879
2369
  exports.CacheService = CacheService;
1880
2370
  exports.FileCacheBackend = FileCacheBackend;
1881
2371
  exports.MS = MS;
1882
2372
  exports.MemoryCacheBackend = MemoryCacheBackend;
2373
+ exports.ModelsDevPricingProvider = ModelsDevPricingProvider;
1883
2374
  exports.SCHEMA_METADATA = require_db.SCHEMA_METADATA;
1884
2375
  exports.SupportedProviders = SupportedProviders;
2376
+ exports.calculateCost = calculateCost;
1885
2377
  exports.chatCompletionCreateParamsBaseSchema = chatCompletionCreateParamsBaseSchema;
1886
2378
  exports.configVariantsSchema = require_db.configVariantsSchema;
1887
2379
  exports.configsSchema = require_db.configsSchema;
1888
2380
  exports.createDataLayer = createDataLayer;
1889
2381
  exports.createDatabase = require_db.createDatabase;
1890
2382
  exports.createDatabaseFromConnection = require_db.createDatabaseFromConnection;
2383
+ exports.createLLMRequestsDataLayer = createLLMRequestsDataLayer;
1891
2384
  exports.detectDatabaseType = require_db.detectDatabaseType;
2385
+ exports.dollarsToMicroDollars = dollarsToMicroDollars;
1892
2386
  exports.environmentSecretsSchema = require_db.environmentSecretsSchema;
1893
2387
  exports.environmentsSchema = require_db.environmentsSchema;
2388
+ exports.formatCost = formatCost;
1894
2389
  Object.defineProperty(exports, 'gateway', {
1895
2390
  enumerable: true,
1896
2391
  get: function () {
@@ -1898,10 +2393,13 @@ Object.defineProperty(exports, 'gateway', {
1898
2393
  }
1899
2394
  });
1900
2395
  exports.generateId = generateId;
2396
+ exports.getDefaultPricingProvider = getDefaultPricingProvider;
1901
2397
  exports.getMigrations = require_db.getMigrations;
2398
+ exports.llmRequestsSchema = require_db.llmRequestsSchema;
1902
2399
  exports.llmopsConfigSchema = llmopsConfigSchema;
1903
2400
  exports.logger = require_db.logger;
1904
2401
  exports.matchType = require_db.matchType;
2402
+ exports.microDollarsToDollars = microDollarsToDollars;
1905
2403
  exports.parsePartialTableData = require_db.parsePartialTableData;
1906
2404
  exports.parseTableData = require_db.parseTableData;
1907
2405
  exports.runAutoMigrations = require_db.runAutoMigrations;