@llmops/core 0.1.4 → 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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,8 @@
1
- import { A as record, C as _enum, D as literal, E as boolean, M as union, N as unknown, O as number, S as zod_default, T as array, _ as schemas, a as matchType, b as variantsSchema, c as parsePartialTableData, d as validateTableData, f as SCHEMA_METADATA, g as environmentsSchema, h as environmentSecretsSchema, i as getMigrations, j as string, k as object, l as parseTableData, m as configsSchema, n as createDatabaseFromConnection, o as runAutoMigrations, p as configVariantsSchema, r as detectDatabaseType, s as logger, t as createDatabase, u as validatePartialTableData, v as targetingRulesSchema, w as any, x as workspaceSettingsSchema, y as variantVersionsSchema } from "./db-CGY-vZ3u.mjs";
1
+ import { A as object, C as zod_default, D as boolean, E as array, M as string, N as union, O as literal, P as unknown, S as workspaceSettingsSchema, T as any, _ as llmRequestsSchema, a as matchType, b as variantVersionsSchema, c as parsePartialTableData, d as validateTableData, f as SCHEMA_METADATA, g as environmentsSchema, h as environmentSecretsSchema, i as getMigrations, j as record, k as number, l as parseTableData, m as configsSchema, n as createDatabaseFromConnection, o as runAutoMigrations, p as configVariantsSchema, r as detectDatabaseType, s as logger, t as createDatabase, u as validatePartialTableData, v as schemas, w as _enum, x as variantsSchema, y as targetingRulesSchema } from "./db-DSzwrW4p.mjs";
2
2
  import gateway from "@llmops/gateway";
3
+ import { sql } from "kysely";
4
+ import * as fs from "node:fs/promises";
5
+ import * as path from "node:path";
3
6
  import { createRandomStringGenerator } from "@better-auth/utils/random";
4
7
  import { randomBytes, randomUUID } from "node:crypto";
5
8
 
@@ -450,7 +453,7 @@ const authSchema = object({ type: string().min(1, "Auth type is required") }).pa
450
453
  const llmopsConfigSchema = object({
451
454
  database: any(),
452
455
  auth: authSchema,
453
- basePath: string().min(1, "Base path is required and cannot be empty").refine((path) => path.startsWith("/"), "Base path must start with a forward slash"),
456
+ basePath: string().min(1, "Base path is required and cannot be empty").refine((path$1) => path$1.startsWith("/"), "Base path must start with a forward slash"),
454
457
  providers: providersSchema,
455
458
  autoMigrate: union([boolean(), literal("development")]).optional().default(false),
456
459
  schema: string().optional().default("llmops")
@@ -464,6 +467,491 @@ function validateLLMOpsConfig(config) {
464
467
  return result.data;
465
468
  }
466
469
 
470
+ //#endregion
471
+ //#region src/cache/types.ts
472
+ /** Time constants in milliseconds for convenience */
473
+ const MS = {
474
+ "1_MINUTE": 60 * 1e3,
475
+ "5_MINUTES": 300 * 1e3,
476
+ "10_MINUTES": 600 * 1e3,
477
+ "30_MINUTES": 1800 * 1e3,
478
+ "1_HOUR": 3600 * 1e3,
479
+ "6_HOURS": 360 * 60 * 1e3,
480
+ "12_HOURS": 720 * 60 * 1e3,
481
+ "1_DAY": 1440 * 60 * 1e3,
482
+ "7_DAYS": 10080 * 60 * 1e3,
483
+ "30_DAYS": 720 * 60 * 60 * 1e3
484
+ };
485
+
486
+ //#endregion
487
+ //#region src/cache/backends/memory.ts
488
+ var MemoryCacheBackend = class {
489
+ cache = /* @__PURE__ */ new Map();
490
+ stats = {
491
+ hits: 0,
492
+ misses: 0,
493
+ sets: 0,
494
+ deletes: 0,
495
+ size: 0,
496
+ expired: 0
497
+ };
498
+ cleanupInterval;
499
+ maxSize;
500
+ constructor(maxSize = 1e4, cleanupIntervalMs = 6e4) {
501
+ this.maxSize = maxSize;
502
+ this.startCleanup(cleanupIntervalMs);
503
+ }
504
+ startCleanup(intervalMs) {
505
+ this.cleanupInterval = setInterval(() => {
506
+ this.cleanup();
507
+ }, intervalMs);
508
+ }
509
+ getFullKey(key, namespace) {
510
+ return namespace ? `${namespace}:${key}` : key;
511
+ }
512
+ isExpired(entry) {
513
+ return entry.expiresAt !== void 0 && entry.expiresAt <= Date.now();
514
+ }
515
+ evictIfNeeded() {
516
+ if (this.cache.size >= this.maxSize) {
517
+ const entries = Array.from(this.cache.entries());
518
+ entries.sort((a, b) => a[1].createdAt - b[1].createdAt);
519
+ const toRemove = Math.floor(this.maxSize * .1);
520
+ for (let i = 0; i < toRemove && i < entries.length; i++) this.cache.delete(entries[i][0]);
521
+ }
522
+ }
523
+ async get(key, namespace) {
524
+ const fullKey = this.getFullKey(key, namespace);
525
+ const entry = this.cache.get(fullKey);
526
+ if (!entry) {
527
+ this.stats.misses++;
528
+ return null;
529
+ }
530
+ if (this.isExpired(entry)) {
531
+ this.cache.delete(fullKey);
532
+ this.stats.expired++;
533
+ this.stats.misses++;
534
+ return null;
535
+ }
536
+ this.stats.hits++;
537
+ return entry;
538
+ }
539
+ async set(key, value, options = {}) {
540
+ const fullKey = this.getFullKey(key, options.namespace);
541
+ const now = Date.now();
542
+ const entry = {
543
+ value,
544
+ createdAt: now,
545
+ expiresAt: options.ttl ? now + options.ttl : void 0,
546
+ metadata: options.metadata
547
+ };
548
+ this.evictIfNeeded();
549
+ this.cache.set(fullKey, entry);
550
+ this.stats.sets++;
551
+ this.stats.size = this.cache.size;
552
+ }
553
+ async delete(key, namespace) {
554
+ const fullKey = this.getFullKey(key, namespace);
555
+ const deleted = this.cache.delete(fullKey);
556
+ if (deleted) {
557
+ this.stats.deletes++;
558
+ this.stats.size = this.cache.size;
559
+ }
560
+ return deleted;
561
+ }
562
+ async clear(namespace) {
563
+ if (namespace) {
564
+ const prefix = `${namespace}:`;
565
+ const keysToDelete = Array.from(this.cache.keys()).filter((key) => key.startsWith(prefix));
566
+ for (const key of keysToDelete) this.cache.delete(key);
567
+ this.stats.deletes += keysToDelete.length;
568
+ } else {
569
+ this.stats.deletes += this.cache.size;
570
+ this.cache.clear();
571
+ }
572
+ this.stats.size = this.cache.size;
573
+ }
574
+ async has(key, namespace) {
575
+ const fullKey = this.getFullKey(key, namespace);
576
+ const entry = this.cache.get(fullKey);
577
+ if (!entry) return false;
578
+ if (this.isExpired(entry)) {
579
+ this.cache.delete(fullKey);
580
+ this.stats.expired++;
581
+ return false;
582
+ }
583
+ return true;
584
+ }
585
+ async keys(namespace) {
586
+ const allKeys = Array.from(this.cache.keys());
587
+ if (namespace) {
588
+ const prefix = `${namespace}:`;
589
+ return allKeys.filter((key) => key.startsWith(prefix)).map((key) => key.substring(prefix.length));
590
+ }
591
+ return allKeys;
592
+ }
593
+ async getStats(namespace) {
594
+ if (namespace) {
595
+ const prefix = `${namespace}:`;
596
+ const namespaceKeys = Array.from(this.cache.keys()).filter((key) => key.startsWith(prefix));
597
+ let expired = 0;
598
+ for (const key of namespaceKeys) {
599
+ const entry = this.cache.get(key);
600
+ if (entry && this.isExpired(entry)) expired++;
601
+ }
602
+ return {
603
+ ...this.stats,
604
+ size: namespaceKeys.length,
605
+ expired
606
+ };
607
+ }
608
+ return { ...this.stats };
609
+ }
610
+ async cleanup() {
611
+ let expiredCount = 0;
612
+ for (const [key, entry] of this.cache.entries()) if (this.isExpired(entry)) {
613
+ this.cache.delete(key);
614
+ expiredCount++;
615
+ }
616
+ if (expiredCount > 0) {
617
+ this.stats.expired += expiredCount;
618
+ this.stats.size = this.cache.size;
619
+ }
620
+ }
621
+ async close() {
622
+ if (this.cleanupInterval) {
623
+ clearInterval(this.cleanupInterval);
624
+ this.cleanupInterval = void 0;
625
+ }
626
+ this.cache.clear();
627
+ }
628
+ };
629
+
630
+ //#endregion
631
+ //#region src/cache/backends/file.ts
632
+ /**
633
+ * @file src/cache/backends/file.ts
634
+ * File-based cache backend implementation
635
+ */
636
+ var FileCacheBackend = class {
637
+ cacheFile;
638
+ data = {};
639
+ saveTimer;
640
+ cleanupInterval;
641
+ loaded = false;
642
+ loadPromise;
643
+ stats = {
644
+ hits: 0,
645
+ misses: 0,
646
+ sets: 0,
647
+ deletes: 0,
648
+ size: 0,
649
+ expired: 0
650
+ };
651
+ saveInterval;
652
+ constructor(dataDir = "data", fileName = "cache.json", saveIntervalMs = 1e3, cleanupIntervalMs = 6e4) {
653
+ this.cacheFile = path.join(process.cwd(), dataDir, fileName);
654
+ this.saveInterval = saveIntervalMs;
655
+ this.loadPromise = this.loadCache();
656
+ this.loadPromise.then(() => {
657
+ this.startCleanup(cleanupIntervalMs);
658
+ });
659
+ }
660
+ /** Ensure cache is loaded before any operation */
661
+ async ensureLoaded() {
662
+ if (!this.loaded) await this.loadPromise;
663
+ }
664
+ async ensureDataDir() {
665
+ const dir = path.dirname(this.cacheFile);
666
+ try {
667
+ await fs.mkdir(dir, { recursive: true });
668
+ } catch {}
669
+ }
670
+ async loadCache() {
671
+ try {
672
+ const content = await fs.readFile(this.cacheFile, "utf-8");
673
+ this.data = JSON.parse(content);
674
+ this.updateStats();
675
+ this.loaded = true;
676
+ } catch {
677
+ this.data = {};
678
+ this.loaded = true;
679
+ }
680
+ }
681
+ async saveCache() {
682
+ try {
683
+ await this.ensureDataDir();
684
+ await fs.writeFile(this.cacheFile, JSON.stringify(this.data, null, 2));
685
+ } catch {}
686
+ }
687
+ scheduleSave() {
688
+ if (this.saveTimer) clearTimeout(this.saveTimer);
689
+ this.saveTimer = setTimeout(() => {
690
+ this.saveCache();
691
+ this.saveTimer = void 0;
692
+ }, this.saveInterval);
693
+ }
694
+ startCleanup(intervalMs) {
695
+ this.cleanupInterval = setInterval(() => {
696
+ this.cleanup();
697
+ }, intervalMs);
698
+ }
699
+ isExpired(entry) {
700
+ return entry.expiresAt !== void 0 && entry.expiresAt <= Date.now();
701
+ }
702
+ updateStats() {
703
+ let totalSize = 0;
704
+ let totalExpired = 0;
705
+ for (const namespace of Object.values(this.data)) for (const entry of Object.values(namespace)) {
706
+ totalSize++;
707
+ if (this.isExpired(entry)) totalExpired++;
708
+ }
709
+ this.stats.size = totalSize;
710
+ this.stats.expired = totalExpired;
711
+ }
712
+ getNamespaceData(namespace = "default") {
713
+ if (!this.data[namespace]) this.data[namespace] = {};
714
+ return this.data[namespace];
715
+ }
716
+ async get(key, namespace) {
717
+ await this.ensureLoaded();
718
+ const namespaceData = this.getNamespaceData(namespace);
719
+ const entry = namespaceData[key];
720
+ if (!entry) {
721
+ this.stats.misses++;
722
+ return null;
723
+ }
724
+ if (this.isExpired(entry)) {
725
+ delete namespaceData[key];
726
+ this.stats.expired++;
727
+ this.stats.misses++;
728
+ this.scheduleSave();
729
+ return null;
730
+ }
731
+ this.stats.hits++;
732
+ return entry;
733
+ }
734
+ async set(key, value, options = {}) {
735
+ await this.ensureLoaded();
736
+ const namespace = options.namespace || "default";
737
+ const namespaceData = this.getNamespaceData(namespace);
738
+ const now = Date.now();
739
+ namespaceData[key] = {
740
+ value,
741
+ createdAt: now,
742
+ expiresAt: options.ttl ? now + options.ttl : void 0,
743
+ metadata: options.metadata
744
+ };
745
+ this.stats.sets++;
746
+ this.updateStats();
747
+ this.scheduleSave();
748
+ }
749
+ async delete(key, namespace) {
750
+ const namespaceData = this.getNamespaceData(namespace);
751
+ const existed = key in namespaceData;
752
+ if (existed) {
753
+ delete namespaceData[key];
754
+ this.stats.deletes++;
755
+ this.updateStats();
756
+ this.scheduleSave();
757
+ }
758
+ return existed;
759
+ }
760
+ async clear(namespace) {
761
+ if (namespace) {
762
+ const namespaceData = this.getNamespaceData(namespace);
763
+ const count = Object.keys(namespaceData).length;
764
+ this.data[namespace] = {};
765
+ this.stats.deletes += count;
766
+ } else {
767
+ const totalCount = Object.values(this.data).reduce((sum, ns) => sum + Object.keys(ns).length, 0);
768
+ this.data = {};
769
+ this.stats.deletes += totalCount;
770
+ }
771
+ this.updateStats();
772
+ this.scheduleSave();
773
+ }
774
+ async has(key, namespace) {
775
+ const namespaceData = this.getNamespaceData(namespace);
776
+ const entry = namespaceData[key];
777
+ if (!entry) return false;
778
+ if (this.isExpired(entry)) {
779
+ delete namespaceData[key];
780
+ this.stats.expired++;
781
+ this.scheduleSave();
782
+ return false;
783
+ }
784
+ return true;
785
+ }
786
+ async keys(namespace) {
787
+ if (namespace) {
788
+ const namespaceData = this.getNamespaceData(namespace);
789
+ return Object.keys(namespaceData);
790
+ }
791
+ const allKeys = [];
792
+ for (const namespaceData of Object.values(this.data)) allKeys.push(...Object.keys(namespaceData));
793
+ return allKeys;
794
+ }
795
+ async getStats(namespace) {
796
+ if (namespace) {
797
+ const namespaceData = this.getNamespaceData(namespace);
798
+ const keys = Object.keys(namespaceData);
799
+ let expired = 0;
800
+ for (const key of keys) {
801
+ const entry = namespaceData[key];
802
+ if (this.isExpired(entry)) expired++;
803
+ }
804
+ return {
805
+ ...this.stats,
806
+ size: keys.length,
807
+ expired
808
+ };
809
+ }
810
+ this.updateStats();
811
+ return { ...this.stats };
812
+ }
813
+ async cleanup() {
814
+ let expiredCount = 0;
815
+ let hasChanges = false;
816
+ for (const [, namespaceData] of Object.entries(this.data)) for (const [key, entry] of Object.entries(namespaceData)) if (this.isExpired(entry)) {
817
+ delete namespaceData[key];
818
+ expiredCount++;
819
+ hasChanges = true;
820
+ }
821
+ if (hasChanges) {
822
+ this.stats.expired += expiredCount;
823
+ this.updateStats();
824
+ this.scheduleSave();
825
+ }
826
+ }
827
+ /** Wait for the cache to be ready (file loaded) */
828
+ async waitForReady() {
829
+ await this.loadPromise;
830
+ }
831
+ async close() {
832
+ if (this.saveTimer) {
833
+ clearTimeout(this.saveTimer);
834
+ await this.saveCache();
835
+ }
836
+ if (this.cleanupInterval) {
837
+ clearInterval(this.cleanupInterval);
838
+ this.cleanupInterval = void 0;
839
+ }
840
+ }
841
+ };
842
+
843
+ //#endregion
844
+ //#region src/cache/service.ts
845
+ /**
846
+ * @file src/cache/service.ts
847
+ * Unified cache service with pluggable backends
848
+ */
849
+ var CacheService = class {
850
+ backend;
851
+ defaultTtl;
852
+ constructor(config) {
853
+ this.defaultTtl = config.defaultTtl;
854
+ this.backend = this.createBackend(config);
855
+ }
856
+ createBackend(config) {
857
+ switch (config.backend) {
858
+ case "memory": return new MemoryCacheBackend(config.maxSize, config.cleanupInterval);
859
+ case "file": return new FileCacheBackend(config.dataDir, config.fileName, config.saveInterval, config.cleanupInterval);
860
+ default: throw new Error(`Unsupported cache backend: ${config.backend}`);
861
+ }
862
+ }
863
+ /** Get a value from the cache */
864
+ async get(key, namespace) {
865
+ const entry = await this.backend.get(key, namespace);
866
+ return entry ? entry.value : null;
867
+ }
868
+ /** Get the full cache entry (with metadata) */
869
+ async getEntry(key, namespace) {
870
+ return this.backend.get(key, namespace);
871
+ }
872
+ /** Set a value in the cache */
873
+ async set(key, value, options = {}) {
874
+ const finalOptions = {
875
+ ...options,
876
+ ttl: options.ttl ?? this.defaultTtl
877
+ };
878
+ await this.backend.set(key, value, finalOptions);
879
+ }
880
+ /** Set a value with TTL in seconds (convenience method) */
881
+ async setWithTtl(key, value, ttlSeconds, namespace) {
882
+ await this.set(key, value, {
883
+ ttl: ttlSeconds * 1e3,
884
+ namespace
885
+ });
886
+ }
887
+ /** Delete a value from the cache */
888
+ async delete(key, namespace) {
889
+ return this.backend.delete(key, namespace);
890
+ }
891
+ /** Check if a key exists in the cache */
892
+ async has(key, namespace) {
893
+ return this.backend.has(key, namespace);
894
+ }
895
+ /** Get all keys in a namespace */
896
+ async keys(namespace) {
897
+ return this.backend.keys(namespace);
898
+ }
899
+ /** Clear all entries in a namespace (or all entries if no namespace) */
900
+ async clear(namespace) {
901
+ await this.backend.clear(namespace);
902
+ }
903
+ /** Get cache statistics */
904
+ async getStats(namespace) {
905
+ return this.backend.getStats(namespace);
906
+ }
907
+ /** Manually trigger cleanup of expired entries */
908
+ async cleanup() {
909
+ await this.backend.cleanup();
910
+ }
911
+ /** Wait for the backend to be ready */
912
+ async waitForReady() {
913
+ if ("waitForReady" in this.backend) await this.backend.waitForReady();
914
+ }
915
+ /** Close the cache and cleanup resources */
916
+ async close() {
917
+ await this.backend.close();
918
+ }
919
+ /** Get or set pattern - get value, or compute and cache it if not found */
920
+ async getOrSet(key, factory, options = {}) {
921
+ const existing = await this.get(key, options.namespace);
922
+ if (existing !== null) return existing;
923
+ const value = await factory();
924
+ await this.set(key, value, options);
925
+ return value;
926
+ }
927
+ /** Increment a numeric value (simulated atomic operation) */
928
+ async increment(key, delta = 1, options = {}) {
929
+ const newValue = (await this.get(key, options.namespace) || 0) + delta;
930
+ await this.set(key, newValue, options);
931
+ return newValue;
932
+ }
933
+ /** Set multiple values at once */
934
+ async setMany(entries, defaultOptions = {}) {
935
+ const promises = entries.map(({ key, value, options }) => this.set(key, value, {
936
+ ...defaultOptions,
937
+ ...options
938
+ }));
939
+ await Promise.all(promises);
940
+ }
941
+ /** Get multiple values at once */
942
+ async getMany(keys, namespace) {
943
+ const promises = keys.map(async (key) => ({
944
+ key,
945
+ value: await this.get(key, namespace)
946
+ }));
947
+ return Promise.all(promises);
948
+ }
949
+ /** Get the underlying backend (for advanced use cases) */
950
+ getBackend() {
951
+ return this.backend;
952
+ }
953
+ };
954
+
467
955
  //#endregion
468
956
  //#region src/utils/id.ts
469
957
  const generateId = (size) => {
@@ -792,7 +1280,11 @@ const createConfigVariantDataLayer = (db) => {
792
1280
  "version"
793
1281
  ]).where("variantId", "=", configVariant.variantId).orderBy("version", "desc").limit(1).executeTakeFirst();
794
1282
  if (!versionData) throw new LLMOpsError(`No variant version found for variant ${configVariant.variantId}`);
795
- return versionData;
1283
+ return {
1284
+ ...versionData,
1285
+ configId: resolvedConfigId,
1286
+ variantId: configVariant.variantId
1287
+ };
796
1288
  }
797
1289
  };
798
1290
  };
@@ -952,6 +1444,287 @@ const createEnvironmentSecretDataLayer = (db) => {
952
1444
  };
953
1445
  };
954
1446
 
1447
+ //#endregion
1448
+ //#region src/datalayer/llmRequests.ts
1449
+ /**
1450
+ * Schema for inserting a new LLM request log
1451
+ */
1452
+ const insertLLMRequestSchema = zod_default.object({
1453
+ requestId: zod_default.string().uuid(),
1454
+ configId: zod_default.string().uuid().nullable().optional(),
1455
+ variantId: zod_default.string().uuid().nullable().optional(),
1456
+ provider: zod_default.string(),
1457
+ model: zod_default.string(),
1458
+ promptTokens: zod_default.number().int().default(0),
1459
+ completionTokens: zod_default.number().int().default(0),
1460
+ totalTokens: zod_default.number().int().default(0),
1461
+ cachedTokens: zod_default.number().int().default(0),
1462
+ cost: zod_default.number().int().default(0),
1463
+ inputCost: zod_default.number().int().default(0),
1464
+ outputCost: zod_default.number().int().default(0),
1465
+ endpoint: zod_default.string(),
1466
+ statusCode: zod_default.number().int(),
1467
+ latencyMs: zod_default.number().int().default(0),
1468
+ isStreaming: zod_default.boolean().default(false),
1469
+ userId: zod_default.string().nullable().optional(),
1470
+ tags: zod_default.record(zod_default.string(), zod_default.string()).default({})
1471
+ });
1472
+ /**
1473
+ * Schema for listing LLM requests
1474
+ */
1475
+ const listRequestsSchema = zod_default.object({
1476
+ limit: zod_default.number().int().positive().max(1e3).default(100),
1477
+ offset: zod_default.number().int().nonnegative().default(0),
1478
+ configId: zod_default.string().uuid().optional(),
1479
+ provider: zod_default.string().optional(),
1480
+ model: zod_default.string().optional(),
1481
+ startDate: zod_default.date().optional(),
1482
+ endDate: zod_default.date().optional()
1483
+ });
1484
+ /**
1485
+ * Schema for date range queries
1486
+ */
1487
+ const dateRangeSchema = zod_default.object({
1488
+ startDate: zod_default.date(),
1489
+ endDate: zod_default.date()
1490
+ });
1491
+ /**
1492
+ * Schema for cost summary with grouping
1493
+ */
1494
+ const costSummarySchema = zod_default.object({
1495
+ startDate: zod_default.date(),
1496
+ endDate: zod_default.date(),
1497
+ groupBy: zod_default.enum([
1498
+ "day",
1499
+ "hour",
1500
+ "model",
1501
+ "provider",
1502
+ "config"
1503
+ ]).optional()
1504
+ });
1505
+ /**
1506
+ * Helper to create column reference for SQL
1507
+ * Uses sql.ref() to properly quote column names for the database
1508
+ */
1509
+ const col = (name) => sql.ref(name);
1510
+ const tableCol = (table, name) => sql.ref(`${table}.${name}`);
1511
+ const createLLMRequestsDataLayer = (db) => {
1512
+ return {
1513
+ batchInsertRequests: async (requests) => {
1514
+ if (requests.length === 0) return { count: 0 };
1515
+ const validatedRequests = await Promise.all(requests.map(async (req) => {
1516
+ const result = await insertLLMRequestSchema.safeParseAsync(req);
1517
+ if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
1518
+ return result.data;
1519
+ }));
1520
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1521
+ const values = validatedRequests.map((req) => ({
1522
+ id: randomUUID(),
1523
+ requestId: req.requestId,
1524
+ configId: req.configId ?? null,
1525
+ variantId: req.variantId ?? null,
1526
+ provider: req.provider,
1527
+ model: req.model,
1528
+ promptTokens: req.promptTokens,
1529
+ completionTokens: req.completionTokens,
1530
+ totalTokens: req.totalTokens,
1531
+ cachedTokens: req.cachedTokens,
1532
+ cost: req.cost,
1533
+ inputCost: req.inputCost,
1534
+ outputCost: req.outputCost,
1535
+ endpoint: req.endpoint,
1536
+ statusCode: req.statusCode,
1537
+ latencyMs: req.latencyMs,
1538
+ isStreaming: req.isStreaming,
1539
+ userId: req.userId ?? null,
1540
+ tags: JSON.stringify(req.tags),
1541
+ createdAt: now,
1542
+ updatedAt: now
1543
+ }));
1544
+ await db.insertInto("llm_requests").values(values).execute();
1545
+ return { count: values.length };
1546
+ },
1547
+ insertRequest: async (request) => {
1548
+ const result = await insertLLMRequestSchema.safeParseAsync(request);
1549
+ if (!result.success) throw new LLMOpsError(`Invalid request data: ${result.error.message}`);
1550
+ const req = result.data;
1551
+ const now = (/* @__PURE__ */ new Date()).toISOString();
1552
+ return db.insertInto("llm_requests").values({
1553
+ id: randomUUID(),
1554
+ requestId: req.requestId,
1555
+ configId: req.configId ?? null,
1556
+ variantId: req.variantId ?? null,
1557
+ provider: req.provider,
1558
+ model: req.model,
1559
+ promptTokens: req.promptTokens,
1560
+ completionTokens: req.completionTokens,
1561
+ totalTokens: req.totalTokens,
1562
+ cachedTokens: req.cachedTokens,
1563
+ cost: req.cost,
1564
+ inputCost: req.inputCost,
1565
+ outputCost: req.outputCost,
1566
+ endpoint: req.endpoint,
1567
+ statusCode: req.statusCode,
1568
+ latencyMs: req.latencyMs,
1569
+ isStreaming: req.isStreaming,
1570
+ userId: req.userId ?? null,
1571
+ tags: JSON.stringify(req.tags),
1572
+ createdAt: now,
1573
+ updatedAt: now
1574
+ }).returningAll().executeTakeFirst();
1575
+ },
1576
+ listRequests: async (params) => {
1577
+ const result = await listRequestsSchema.safeParseAsync(params || {});
1578
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1579
+ const { limit, offset, configId, provider, model, startDate, endDate } = result.data;
1580
+ let baseQuery = db.selectFrom("llm_requests");
1581
+ if (configId) baseQuery = baseQuery.where("configId", "=", configId);
1582
+ if (provider) baseQuery = baseQuery.where("provider", "=", provider);
1583
+ if (model) baseQuery = baseQuery.where("model", "=", model);
1584
+ if (startDate) baseQuery = baseQuery.where(sql`${col("createdAt")} >= ${startDate.toISOString()}`);
1585
+ if (endDate) baseQuery = baseQuery.where(sql`${col("createdAt")} <= ${endDate.toISOString()}`);
1586
+ const countResult = await baseQuery.select(sql`COUNT(*)`.as("total")).executeTakeFirst();
1587
+ const total = Number(countResult?.total ?? 0);
1588
+ return {
1589
+ data: await baseQuery.selectAll().orderBy("createdAt", "desc").limit(limit).offset(offset).execute(),
1590
+ total,
1591
+ limit,
1592
+ offset
1593
+ };
1594
+ },
1595
+ getRequestByRequestId: async (requestId) => {
1596
+ return db.selectFrom("llm_requests").selectAll().where("requestId", "=", requestId).executeTakeFirst();
1597
+ },
1598
+ getTotalCost: async (params) => {
1599
+ const result = await dateRangeSchema.safeParseAsync(params);
1600
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1601
+ const { startDate, endDate } = result.data;
1602
+ return await db.selectFrom("llm_requests").select([
1603
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1604
+ sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1605
+ sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1606
+ sql`COALESCE(SUM(${col("promptTokens")}), 0)`.as("totalPromptTokens"),
1607
+ sql`COALESCE(SUM(${col("completionTokens")}), 0)`.as("totalCompletionTokens"),
1608
+ sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1609
+ sql`COUNT(*)`.as("requestCount")
1610
+ ]).where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
1611
+ },
1612
+ getCostByModel: async (params) => {
1613
+ const result = await dateRangeSchema.safeParseAsync(params);
1614
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1615
+ const { startDate, endDate } = result.data;
1616
+ return db.selectFrom("llm_requests").select([
1617
+ "provider",
1618
+ "model",
1619
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1620
+ sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1621
+ sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1622
+ sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1623
+ sql`COUNT(*)`.as("requestCount"),
1624
+ sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
1625
+ ]).where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(["provider", "model"]).orderBy(sql`SUM(${col("cost")})`, "desc").execute();
1626
+ },
1627
+ getCostByProvider: async (params) => {
1628
+ const result = await dateRangeSchema.safeParseAsync(params);
1629
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1630
+ const { startDate, endDate } = result.data;
1631
+ return db.selectFrom("llm_requests").select([
1632
+ "provider",
1633
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1634
+ sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1635
+ sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1636
+ sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1637
+ sql`COUNT(*)`.as("requestCount"),
1638
+ sql`AVG(${col("latencyMs")})`.as("avgLatencyMs")
1639
+ ]).where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy("provider").orderBy(sql`SUM(${col("cost")})`, "desc").execute();
1640
+ },
1641
+ getCostByConfig: async (params) => {
1642
+ const result = await dateRangeSchema.safeParseAsync(params);
1643
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1644
+ const { startDate, endDate } = result.data;
1645
+ return db.selectFrom("llm_requests").leftJoin("configs", "llm_requests.configId", "configs.id").select([
1646
+ "llm_requests.configId",
1647
+ "configs.name as configName",
1648
+ "configs.slug as configSlug",
1649
+ sql`COALESCE(SUM(${tableCol("llm_requests", "cost")}), 0)`.as("totalCost"),
1650
+ sql`COALESCE(SUM(${tableCol("llm_requests", "inputCost")}), 0)`.as("totalInputCost"),
1651
+ sql`COALESCE(SUM(${tableCol("llm_requests", "outputCost")}), 0)`.as("totalOutputCost"),
1652
+ sql`COALESCE(SUM(${tableCol("llm_requests", "totalTokens")}), 0)`.as("totalTokens"),
1653
+ sql`COUNT(*)`.as("requestCount")
1654
+ ]).where(sql`${tableCol("llm_requests", "createdAt")} >= ${startDate.toISOString()}`).where(sql`${tableCol("llm_requests", "createdAt")} <= ${endDate.toISOString()}`).groupBy([
1655
+ "llm_requests.configId",
1656
+ "configs.name",
1657
+ "configs.slug"
1658
+ ]).orderBy(sql`SUM(${tableCol("llm_requests", "cost")})`, "desc").execute();
1659
+ },
1660
+ getDailyCosts: async (params) => {
1661
+ const result = await dateRangeSchema.safeParseAsync(params);
1662
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1663
+ const { startDate, endDate } = result.data;
1664
+ return db.selectFrom("llm_requests").select([
1665
+ sql`DATE(${col("createdAt")})`.as("date"),
1666
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1667
+ sql`COALESCE(SUM(${col("inputCost")}), 0)`.as("totalInputCost"),
1668
+ sql`COALESCE(SUM(${col("outputCost")}), 0)`.as("totalOutputCost"),
1669
+ sql`COALESCE(SUM(${col("totalTokens")}), 0)`.as("totalTokens"),
1670
+ sql`COUNT(*)`.as("requestCount")
1671
+ ]).where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`).groupBy(sql`DATE(${col("createdAt")})`).orderBy(sql`DATE(${col("createdAt")})`, "asc").execute();
1672
+ },
1673
+ getCostSummary: async (params) => {
1674
+ const result = await costSummarySchema.safeParseAsync(params);
1675
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1676
+ const { startDate, endDate, groupBy } = result.data;
1677
+ const baseQuery = db.selectFrom("llm_requests").where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`);
1678
+ switch (groupBy) {
1679
+ case "day": return baseQuery.select([
1680
+ sql`DATE(${col("createdAt")})`.as("groupKey"),
1681
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1682
+ sql`COUNT(*)`.as("requestCount")
1683
+ ]).groupBy(sql`DATE(${col("createdAt")})`).orderBy(sql`DATE(${col("createdAt")})`, "asc").execute();
1684
+ case "hour": return baseQuery.select([
1685
+ sql`DATE_TRUNC('hour', ${col("createdAt")})`.as("groupKey"),
1686
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1687
+ sql`COUNT(*)`.as("requestCount")
1688
+ ]).groupBy(sql`DATE_TRUNC('hour', ${col("createdAt")})`).orderBy(sql`DATE_TRUNC('hour', ${col("createdAt")})`, "asc").execute();
1689
+ case "model": return baseQuery.select([
1690
+ sql`${col("provider")} || '/' || ${col("model")}`.as("groupKey"),
1691
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1692
+ sql`COUNT(*)`.as("requestCount")
1693
+ ]).groupBy(["provider", "model"]).orderBy(sql`SUM(${col("cost")})`, "desc").execute();
1694
+ case "provider": return baseQuery.select([
1695
+ sql`${col("provider")}`.as("groupKey"),
1696
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1697
+ sql`COUNT(*)`.as("requestCount")
1698
+ ]).groupBy("provider").orderBy(sql`SUM(${col("cost")})`, "desc").execute();
1699
+ case "config": return baseQuery.select([
1700
+ sql`COALESCE(${col("configId")}::text, 'no-config')`.as("groupKey"),
1701
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1702
+ sql`COUNT(*)`.as("requestCount")
1703
+ ]).groupBy("configId").orderBy(sql`SUM(${col("cost")})`, "desc").execute();
1704
+ default: return baseQuery.select([
1705
+ sql`'total'`.as("groupKey"),
1706
+ sql`COALESCE(SUM(${col("cost")}), 0)`.as("totalCost"),
1707
+ sql`COUNT(*)`.as("requestCount")
1708
+ ]).execute();
1709
+ }
1710
+ },
1711
+ getRequestStats: async (params) => {
1712
+ const result = await dateRangeSchema.safeParseAsync(params);
1713
+ if (!result.success) throw new LLMOpsError(`Invalid parameters: ${result.error.message}`);
1714
+ const { startDate, endDate } = result.data;
1715
+ return await db.selectFrom("llm_requests").select([
1716
+ sql`COUNT(*)`.as("totalRequests"),
1717
+ sql`COUNT(CASE WHEN ${col("statusCode")} >= 200 AND ${col("statusCode")} < 300 THEN 1 END)`.as("successfulRequests"),
1718
+ sql`COUNT(CASE WHEN ${col("statusCode")} >= 400 THEN 1 END)`.as("failedRequests"),
1719
+ sql`COUNT(CASE WHEN ${col("isStreaming")} = true THEN 1 END)`.as("streamingRequests"),
1720
+ sql`AVG(${col("latencyMs")})`.as("avgLatencyMs"),
1721
+ sql`MAX(${col("latencyMs")})`.as("maxLatencyMs"),
1722
+ sql`MIN(${col("latencyMs")})`.as("minLatencyMs")
1723
+ ]).where(sql`${col("createdAt")} >= ${startDate.toISOString()}`).where(sql`${col("createdAt")} <= ${endDate.toISOString()}`).executeTakeFirst();
1724
+ }
1725
+ };
1726
+ };
1727
+
955
1728
  //#endregion
956
1729
  //#region src/datalayer/targetingRules.ts
957
1730
  const createTargetingRule = zod_default.object({
@@ -1378,6 +2151,7 @@ const createDataLayer = async (db) => {
1378
2151
  ...createConfigVariantDataLayer(db),
1379
2152
  ...createEnvironmentDataLayer(db),
1380
2153
  ...createEnvironmentSecretDataLayer(db),
2154
+ ...createLLMRequestsDataLayer(db),
1381
2155
  ...createTargetingRulesDataLayer(db),
1382
2156
  ...createVariantDataLayer(db),
1383
2157
  ...createVariantVersionsDataLayer(db),
@@ -1386,4 +2160,207 @@ const createDataLayer = async (db) => {
1386
2160
  };
1387
2161
 
1388
2162
  //#endregion
1389
- export { SCHEMA_METADATA, SupportedProviders, chatCompletionCreateParamsBaseSchema, configVariantsSchema, configsSchema, createDataLayer, createDatabase, createDatabaseFromConnection, detectDatabaseType, environmentSecretsSchema, environmentsSchema, gateway, generateId, getMigrations, llmopsConfigSchema, logger, matchType, parsePartialTableData, parseTableData, runAutoMigrations, schemas, targetingRulesSchema, validateLLMOpsConfig, validatePartialTableData, validateTableData, variantJsonDataSchema, variantVersionsSchema, variantsSchema, workspaceSettingsSchema };
2163
+ //#region src/pricing/calculator.ts
2164
+ /**
2165
+ * Calculate the cost of an LLM request in micro-dollars
2166
+ *
2167
+ * Micro-dollars are used to avoid floating-point precision issues:
2168
+ * - 1 dollar = 1,000,000 micro-dollars
2169
+ * - $0.001 = 1,000 micro-dollars
2170
+ * - $0.000001 = 1 micro-dollar
2171
+ *
2172
+ * @param usage - Token usage data from the LLM response
2173
+ * @param pricing - Model pricing information
2174
+ * @returns Cost breakdown in micro-dollars
2175
+ *
2176
+ * @example
2177
+ * ```typescript
2178
+ * const usage = { promptTokens: 1000, completionTokens: 500 };
2179
+ * const pricing = { inputCostPer1M: 2.5, outputCostPer1M: 10.0 };
2180
+ * const cost = calculateCost(usage, pricing);
2181
+ * // cost = { inputCost: 2500, outputCost: 5000, totalCost: 7500 }
2182
+ * // In dollars: $0.0025 input + $0.005 output = $0.0075 total
2183
+ * ```
2184
+ */
2185
+ function calculateCost(usage, pricing) {
2186
+ const inputCost = Math.round(usage.promptTokens * pricing.inputCostPer1M);
2187
+ const outputCost = Math.round(usage.completionTokens * pricing.outputCostPer1M);
2188
+ return {
2189
+ inputCost,
2190
+ outputCost,
2191
+ totalCost: inputCost + outputCost
2192
+ };
2193
+ }
2194
+ /**
2195
+ * Convert micro-dollars to dollars
2196
+ *
2197
+ * @param microDollars - Amount in micro-dollars
2198
+ * @returns Amount in dollars
2199
+ *
2200
+ * @example
2201
+ * ```typescript
2202
+ * microDollarsToDollars(7500); // 0.0075
2203
+ * microDollarsToDollars(1000000); // 1.0
2204
+ * ```
2205
+ */
2206
+ function microDollarsToDollars(microDollars) {
2207
+ return microDollars / 1e6;
2208
+ }
2209
+ /**
2210
+ * Convert dollars to micro-dollars
2211
+ *
2212
+ * @param dollars - Amount in dollars
2213
+ * @returns Amount in micro-dollars (rounded to nearest integer)
2214
+ *
2215
+ * @example
2216
+ * ```typescript
2217
+ * dollarsToMicroDollars(0.0075); // 7500
2218
+ * dollarsToMicroDollars(1.0); // 1000000
2219
+ * ```
2220
+ */
2221
+ function dollarsToMicroDollars(dollars) {
2222
+ return Math.round(dollars * 1e6);
2223
+ }
2224
+ /**
2225
+ * Format micro-dollars as a human-readable dollar string
2226
+ *
2227
+ * @param microDollars - Amount in micro-dollars
2228
+ * @param decimals - Number of decimal places (default: 6)
2229
+ * @returns Formatted dollar string
2230
+ *
2231
+ * @example
2232
+ * ```typescript
2233
+ * formatCost(7500); // "$0.007500"
2234
+ * formatCost(1234567, 2); // "$1.23"
2235
+ * ```
2236
+ */
2237
+ function formatCost(microDollars, decimals = 6) {
2238
+ return `$${microDollarsToDollars(microDollars).toFixed(decimals)}`;
2239
+ }
2240
+
2241
+ //#endregion
2242
+ //#region src/pricing/provider.ts
2243
+ const MODELS_DEV_API = "https://models.dev/api.json";
2244
+ /**
2245
+ * Pricing provider that fetches data from models.dev API
2246
+ *
2247
+ * Features:
2248
+ * - Caches pricing data with configurable TTL (default 5 minutes)
2249
+ * - Supports fallback to local cache on fetch failure
2250
+ * - Thread-safe cache refresh
2251
+ */
2252
+ var ModelsDevPricingProvider = class {
2253
+ cache = /* @__PURE__ */ new Map();
2254
+ lastFetch = 0;
2255
+ cacheTTL;
2256
+ fetchPromise = null;
2257
+ ready = false;
2258
+ /**
2259
+ * Create a new ModelsDevPricingProvider
2260
+ *
2261
+ * @param cacheTTL - Cache TTL in milliseconds (default: 5 minutes)
2262
+ */
2263
+ constructor(cacheTTL = 300 * 1e3) {
2264
+ this.cacheTTL = cacheTTL;
2265
+ }
2266
+ /**
2267
+ * Generate a cache key for a provider/model combination
2268
+ */
2269
+ getCacheKey(provider, model) {
2270
+ return `${provider.toLowerCase()}:${model.toLowerCase()}`;
2271
+ }
2272
+ /**
2273
+ * Fetch pricing data from models.dev API
2274
+ */
2275
+ async fetchPricingData() {
2276
+ try {
2277
+ logger.debug("[Pricing] Fetching pricing data from models.dev");
2278
+ const response = await fetch(MODELS_DEV_API);
2279
+ if (!response.ok) throw new Error(`Failed to fetch models.dev API: ${response.status}`);
2280
+ const data = await response.json();
2281
+ this.cache.clear();
2282
+ for (const [providerId, provider] of Object.entries(data)) {
2283
+ if (!provider.models) continue;
2284
+ for (const [_modelId, model] of Object.entries(provider.models)) {
2285
+ if (!model.cost) continue;
2286
+ const cacheKey = this.getCacheKey(providerId, model.id);
2287
+ this.cache.set(cacheKey, {
2288
+ inputCostPer1M: model.cost.input ?? 0,
2289
+ outputCostPer1M: model.cost.output ?? 0,
2290
+ cacheReadCostPer1M: model.cost.cache_read,
2291
+ cacheWriteCostPer1M: model.cost.cache_write,
2292
+ reasoningCostPer1M: model.cost.reasoning
2293
+ });
2294
+ const nameKey = this.getCacheKey(providerId, model.name);
2295
+ if (nameKey !== cacheKey) this.cache.set(nameKey, this.cache.get(cacheKey));
2296
+ }
2297
+ }
2298
+ this.lastFetch = Date.now();
2299
+ this.ready = true;
2300
+ logger.debug(`[Pricing] Cached pricing for ${this.cache.size} models from models.dev`);
2301
+ } catch (error) {
2302
+ logger.error(`[Pricing] Failed to fetch pricing data: ${error instanceof Error ? error.message : String(error)}`);
2303
+ if (this.cache.size === 0) throw error;
2304
+ }
2305
+ }
2306
+ /**
2307
+ * Ensure cache is fresh, fetching if necessary
2308
+ */
2309
+ async ensureFreshCache() {
2310
+ if (!(Date.now() - this.lastFetch > this.cacheTTL) && this.cache.size > 0) return;
2311
+ if (!this.fetchPromise) this.fetchPromise = this.fetchPricingData().finally(() => {
2312
+ this.fetchPromise = null;
2313
+ });
2314
+ await this.fetchPromise;
2315
+ }
2316
+ /**
2317
+ * Get pricing for a specific model
2318
+ */
2319
+ async getModelPricing(provider, model) {
2320
+ await this.ensureFreshCache();
2321
+ const cacheKey = this.getCacheKey(provider, model);
2322
+ const pricing = this.cache.get(cacheKey);
2323
+ if (!pricing) {
2324
+ logger.debug(`[Pricing] No pricing found for ${provider}/${model}, trying partial match`);
2325
+ for (const [key, value] of this.cache.entries()) if (key.startsWith(`${provider.toLowerCase()}:`)) {
2326
+ const modelPart = key.split(":")[1];
2327
+ if (model.toLowerCase().includes(modelPart)) {
2328
+ logger.debug(`[Pricing] Found partial match: ${key}`);
2329
+ return value;
2330
+ }
2331
+ }
2332
+ return null;
2333
+ }
2334
+ return pricing;
2335
+ }
2336
+ /**
2337
+ * Force refresh the pricing cache
2338
+ */
2339
+ async refreshCache() {
2340
+ this.lastFetch = 0;
2341
+ await this.ensureFreshCache();
2342
+ }
2343
+ /**
2344
+ * Check if the provider is ready
2345
+ */
2346
+ isReady() {
2347
+ return this.ready;
2348
+ }
2349
+ /**
2350
+ * Get the number of cached models (for debugging)
2351
+ */
2352
+ getCacheSize() {
2353
+ return this.cache.size;
2354
+ }
2355
+ };
2356
+ let defaultProvider = null;
2357
+ /**
2358
+ * Get the default pricing provider instance
2359
+ */
2360
+ function getDefaultPricingProvider() {
2361
+ if (!defaultProvider) defaultProvider = new ModelsDevPricingProvider();
2362
+ return defaultProvider;
2363
+ }
2364
+
2365
+ //#endregion
2366
+ export { CacheService, FileCacheBackend, MS, MemoryCacheBackend, ModelsDevPricingProvider, SCHEMA_METADATA, SupportedProviders, calculateCost, chatCompletionCreateParamsBaseSchema, configVariantsSchema, configsSchema, createDataLayer, createDatabase, createDatabaseFromConnection, createLLMRequestsDataLayer, detectDatabaseType, dollarsToMicroDollars, environmentSecretsSchema, environmentsSchema, formatCost, gateway, generateId, getDefaultPricingProvider, getMigrations, llmRequestsSchema, llmopsConfigSchema, logger, matchType, microDollarsToDollars, parsePartialTableData, parseTableData, runAutoMigrations, schemas, targetingRulesSchema, validateLLMOpsConfig, validatePartialTableData, validateTableData, variantJsonDataSchema, variantVersionsSchema, variantsSchema, workspaceSettingsSchema };