@llmops/core 0.2.13 → 0.3.0-beta.1
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/{bun-sqlite-dialect-Bxz4B97L.cjs → bun-sqlite-dialect-BNaQHMpk.cjs} +1 -1
- package/dist/db/index.cjs +2 -2
- package/dist/db/index.d.cts +1 -1
- package/dist/db/index.d.mts +1 -1
- package/dist/db/index.mjs +1 -1
- package/dist/{db-DohlAqJU.mjs → db-B-EsQtOz.mjs} +5 -0
- package/dist/{db-BOe6mM51.cjs → db-Du2xmkGS.cjs} +8 -3
- package/dist/{index-C5xtb4gO.d.cts → index-COkIT6TH.d.mts} +15 -1
- package/dist/{index-BO5Rse5J.d.mts → index-DX05tkNg.d.cts} +15 -1
- package/dist/index.cjs +386 -15
- package/dist/index.d.cts +214 -2
- package/dist/index.d.mts +214 -2
- package/dist/index.mjs +382 -15
- package/dist/{neon-dialect-C0GZuGot.cjs → neon-dialect-B-Q6mmbI.cjs} +1 -1
- package/dist/{neon-dialect-BQey5lUw.cjs → neon-dialect-BR1nZmKX.cjs} +1 -1
- package/dist/{node-sqlite-dialect-N8j5UsT-.cjs → node-sqlite-dialect-DpdAEbyp.cjs} +1 -1
- package/package.json +4 -2
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as literal, C as variantsSchema, D as any, E as _enum, F as union, I as unknown, M as object, N as record, O as array, P as string, S as variantVersionsSchema, T as zod_default, _ as environmentsSchema, a as matchType, b as schemas, c as logger, d as validatePartialTableData, f as validateTableData, g as environmentSecretsSchema, h as configsSchema, i as getMigrations, j as number, k as boolean, l as parsePartialTableData, m as configVariantsSchema, n as createDatabaseFromConnection, o as runAutoMigrations, p as SCHEMA_METADATA, r as detectDatabaseType, s as getAuthClientOptions, t as createDatabase, u as parseTableData, v as llmRequestsSchema, w as workspaceSettingsSchema, x as targetingRulesSchema, y as providerConfigsSchema } from "./db-
|
|
1
|
+
import { A as literal, C as variantsSchema, D as any, E as _enum, F as union, I as unknown, M as object, N as record, O as array, P as string, S as variantVersionsSchema, T as zod_default, _ as environmentsSchema, a as matchType, b as schemas, c as logger, d as validatePartialTableData, f as validateTableData, g as environmentSecretsSchema, h as configsSchema, i as getMigrations, j as number, k as boolean, l as parsePartialTableData, m as configVariantsSchema, n as createDatabaseFromConnection, o as runAutoMigrations, p as SCHEMA_METADATA, r as detectDatabaseType, s as getAuthClientOptions, t as createDatabase, u as parseTableData, v as llmRequestsSchema, w as workspaceSettingsSchema, x as targetingRulesSchema, y as providerConfigsSchema } from "./db-B-EsQtOz.mjs";
|
|
2
2
|
import { n as executeWithSchema, t as createNeonDialect } from "./neon-dialect-DySGBYUi.mjs";
|
|
3
3
|
import gateway from "@llmops/gateway";
|
|
4
4
|
import { sql } from "kysely";
|
|
@@ -6,6 +6,7 @@ import * as fs from "node:fs/promises";
|
|
|
6
6
|
import * as path from "node:path";
|
|
7
7
|
import { createRandomStringGenerator } from "@better-auth/utils/random";
|
|
8
8
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
9
|
+
import jsonLogic from "json-logic-js";
|
|
9
10
|
|
|
10
11
|
//#region src/providers/supported-providers.ts
|
|
11
12
|
/**
|
|
@@ -837,7 +838,10 @@ var CacheService = class {
|
|
|
837
838
|
}
|
|
838
839
|
createBackend(config) {
|
|
839
840
|
switch (config.backend) {
|
|
840
|
-
case "memory": return new MemoryCacheBackend(
|
|
841
|
+
case "memory": return new MemoryCacheBackend({
|
|
842
|
+
maxSize: config.maxSize,
|
|
843
|
+
cleanupIntervalMs: config.cleanupInterval
|
|
844
|
+
});
|
|
841
845
|
case "file": return new FileCacheBackend(config.dataDir, config.fileName, config.saveInterval, config.cleanupInterval);
|
|
842
846
|
default: throw new Error(`Unsupported cache backend: ${config.backend}`);
|
|
843
847
|
}
|
|
@@ -1226,9 +1230,9 @@ const createConfigVariantDataLayer = (db) => {
|
|
|
1226
1230
|
const value = await getVariantJsonDataForConfig.safeParseAsync(params);
|
|
1227
1231
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1228
1232
|
const { configId: configIdOrSlug, envSecret } = value.data;
|
|
1229
|
-
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1233
|
+
const UUID_REGEX$1 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1230
1234
|
let resolvedConfigId;
|
|
1231
|
-
if (UUID_REGEX.test(configIdOrSlug)) resolvedConfigId = configIdOrSlug;
|
|
1235
|
+
if (UUID_REGEX$1.test(configIdOrSlug)) resolvedConfigId = configIdOrSlug;
|
|
1232
1236
|
else {
|
|
1233
1237
|
const config = await db.selectFrom("configs").select("id").where("slug", "=", configIdOrSlug).executeTakeFirst();
|
|
1234
1238
|
if (!config) throw new LLMOpsError(`Config not found: ${configIdOrSlug}`);
|
|
@@ -1264,7 +1268,7 @@ const createConfigVariantDataLayer = (db) => {
|
|
|
1264
1268
|
if (!versionData) throw new LLMOpsError(`No variant version found for variant ${configVariant.variantId}`);
|
|
1265
1269
|
let finalProvider = versionData.provider;
|
|
1266
1270
|
let providerConfigId = null;
|
|
1267
|
-
if (UUID_REGEX.test(versionData.provider)) {
|
|
1271
|
+
if (UUID_REGEX$1.test(versionData.provider)) {
|
|
1268
1272
|
const providerConfig = await db.selectFrom("provider_configs").select(["id", "providerId"]).where("id", "=", versionData.provider).executeTakeFirst();
|
|
1269
1273
|
if (providerConfig) {
|
|
1270
1274
|
finalProvider = providerConfig.providerId;
|
|
@@ -1795,20 +1799,40 @@ const createLLMRequestsDataLayer = (db) => {
|
|
|
1795
1799
|
|
|
1796
1800
|
//#endregion
|
|
1797
1801
|
//#region src/datalayer/providerConfigs.ts
|
|
1802
|
+
/**
|
|
1803
|
+
* Generate a unique slug for a provider config.
|
|
1804
|
+
* If the base slug already exists, appends -01, -02, etc.
|
|
1805
|
+
*/
|
|
1806
|
+
async function generateUniqueSlug(db, baseSlug) {
|
|
1807
|
+
const existing = await db.selectFrom("provider_configs").select("slug").where("slug", "like", `${baseSlug}%`).execute();
|
|
1808
|
+
if (existing.length === 0) return baseSlug;
|
|
1809
|
+
const existingSlugs = new Set(existing.map((e) => e.slug));
|
|
1810
|
+
if (!existingSlugs.has(baseSlug)) return baseSlug;
|
|
1811
|
+
let counter = 1;
|
|
1812
|
+
while (counter < 100) {
|
|
1813
|
+
const candidateSlug = `${baseSlug}-${counter.toString().padStart(2, "0")}`;
|
|
1814
|
+
if (!existingSlugs.has(candidateSlug)) return candidateSlug;
|
|
1815
|
+
counter++;
|
|
1816
|
+
}
|
|
1817
|
+
return `${baseSlug}-${randomUUID().slice(0, 8)}`;
|
|
1818
|
+
}
|
|
1798
1819
|
const createProviderConfig = zod_default.object({
|
|
1799
1820
|
providerId: zod_default.string().min(1),
|
|
1821
|
+
slug: zod_default.string().nullable().optional(),
|
|
1800
1822
|
name: zod_default.string().nullable().optional(),
|
|
1801
1823
|
config: zod_default.record(zod_default.string(), zod_default.unknown()),
|
|
1802
1824
|
enabled: zod_default.boolean().optional().default(true)
|
|
1803
1825
|
});
|
|
1804
1826
|
const updateProviderConfig = zod_default.object({
|
|
1805
1827
|
id: zod_default.uuidv4(),
|
|
1828
|
+
slug: zod_default.string().nullable().optional(),
|
|
1806
1829
|
name: zod_default.string().nullable().optional(),
|
|
1807
1830
|
config: zod_default.record(zod_default.string(), zod_default.unknown()).optional(),
|
|
1808
1831
|
enabled: zod_default.boolean().optional()
|
|
1809
1832
|
});
|
|
1810
1833
|
const getProviderConfigById = zod_default.object({ id: zod_default.uuidv4() });
|
|
1811
1834
|
const getProviderConfigByProviderId = zod_default.object({ providerId: zod_default.string().min(1) });
|
|
1835
|
+
const getProviderConfigBySlug = zod_default.object({ slug: zod_default.string().min(1) });
|
|
1812
1836
|
const deleteProviderConfig = zod_default.object({ id: zod_default.uuidv4() });
|
|
1813
1837
|
const listProviderConfigs = zod_default.object({
|
|
1814
1838
|
limit: zod_default.number().int().positive().optional(),
|
|
@@ -1819,10 +1843,12 @@ const createProviderConfigsDataLayer = (db) => {
|
|
|
1819
1843
|
createProviderConfig: async (params) => {
|
|
1820
1844
|
const value = await createProviderConfig.safeParseAsync(params);
|
|
1821
1845
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1822
|
-
const { providerId, name, config, enabled } = value.data;
|
|
1846
|
+
const { providerId, slug, name, config, enabled } = value.data;
|
|
1847
|
+
const finalSlug = slug ?? await generateUniqueSlug(db, providerId);
|
|
1823
1848
|
return db.insertInto("provider_configs").values({
|
|
1824
1849
|
id: randomUUID(),
|
|
1825
1850
|
providerId,
|
|
1851
|
+
slug: finalSlug,
|
|
1826
1852
|
name: name ?? null,
|
|
1827
1853
|
config: JSON.stringify(config),
|
|
1828
1854
|
enabled,
|
|
@@ -1833,8 +1859,9 @@ const createProviderConfigsDataLayer = (db) => {
|
|
|
1833
1859
|
updateProviderConfig: async (params) => {
|
|
1834
1860
|
const value = await updateProviderConfig.safeParseAsync(params);
|
|
1835
1861
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1836
|
-
const { id, name, config, enabled } = value.data;
|
|
1862
|
+
const { id, slug, name, config, enabled } = value.data;
|
|
1837
1863
|
const updateData = { updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
1864
|
+
if (slug !== void 0) updateData.slug = slug;
|
|
1838
1865
|
if (name !== void 0) updateData.name = name;
|
|
1839
1866
|
if (config !== void 0) updateData.config = JSON.stringify(config);
|
|
1840
1867
|
if (enabled !== void 0) updateData.enabled = enabled;
|
|
@@ -1852,6 +1879,12 @@ const createProviderConfigsDataLayer = (db) => {
|
|
|
1852
1879
|
const { providerId } = value.data;
|
|
1853
1880
|
return db.selectFrom("provider_configs").selectAll().where("providerId", "=", providerId).executeTakeFirst();
|
|
1854
1881
|
},
|
|
1882
|
+
getProviderConfigBySlug: async (params) => {
|
|
1883
|
+
const value = await getProviderConfigBySlug.safeParseAsync(params);
|
|
1884
|
+
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1885
|
+
const { slug } = value.data;
|
|
1886
|
+
return db.selectFrom("provider_configs").selectAll().where("slug", "=", slug).executeTakeFirst();
|
|
1887
|
+
},
|
|
1855
1888
|
deleteProviderConfig: async (params) => {
|
|
1856
1889
|
const value = await deleteProviderConfig.safeParseAsync(params);
|
|
1857
1890
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
@@ -1871,17 +1904,23 @@ const createProviderConfigsDataLayer = (db) => {
|
|
|
1871
1904
|
upsertProviderConfig: async (params) => {
|
|
1872
1905
|
const value = await createProviderConfig.safeParseAsync(params);
|
|
1873
1906
|
if (!value.success) throw new LLMOpsError(`Invalid parameters: ${value.error.message}`);
|
|
1874
|
-
const { providerId, name, config, enabled } = value.data;
|
|
1907
|
+
const { providerId, slug, name, config, enabled } = value.data;
|
|
1875
1908
|
const existing = await db.selectFrom("provider_configs").selectAll().where("providerId", "=", providerId).executeTakeFirst();
|
|
1876
|
-
if (existing)
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1909
|
+
if (existing) {
|
|
1910
|
+
const finalSlug$1 = slug ?? existing.slug ?? await generateUniqueSlug(db, providerId);
|
|
1911
|
+
return db.updateTable("provider_configs").set({
|
|
1912
|
+
slug: finalSlug$1,
|
|
1913
|
+
name: name ?? existing.name,
|
|
1914
|
+
config: JSON.stringify(config),
|
|
1915
|
+
enabled,
|
|
1916
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1917
|
+
}).where("id", "=", existing.id).returningAll().executeTakeFirst();
|
|
1918
|
+
}
|
|
1919
|
+
const finalSlug = slug ?? await generateUniqueSlug(db, providerId);
|
|
1882
1920
|
return db.insertInto("provider_configs").values({
|
|
1883
1921
|
id: randomUUID(),
|
|
1884
1922
|
providerId,
|
|
1923
|
+
slug: finalSlug,
|
|
1885
1924
|
name: name ?? null,
|
|
1886
1925
|
config: JSON.stringify(config),
|
|
1887
1926
|
enabled,
|
|
@@ -2578,4 +2617,332 @@ function getDefaultPricingProvider() {
|
|
|
2578
2617
|
}
|
|
2579
2618
|
|
|
2580
2619
|
//#endregion
|
|
2581
|
-
|
|
2620
|
+
//#region src/manifest/builder.ts
|
|
2621
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
2622
|
+
/**
|
|
2623
|
+
* Builds the gateway routing manifest from database
|
|
2624
|
+
*/
|
|
2625
|
+
var ManifestBuilder = class {
|
|
2626
|
+
constructor(db) {
|
|
2627
|
+
this.db = db;
|
|
2628
|
+
}
|
|
2629
|
+
/**
|
|
2630
|
+
* Build the complete routing manifest from database
|
|
2631
|
+
*/
|
|
2632
|
+
async build() {
|
|
2633
|
+
const [configs, environments, environmentSecrets, targetingRules, configVariants, variantVersions, providerConfigs] = await Promise.all([
|
|
2634
|
+
this.db.selectFrom("configs").selectAll().execute(),
|
|
2635
|
+
this.db.selectFrom("environments").selectAll().execute(),
|
|
2636
|
+
this.db.selectFrom("environment_secrets").selectAll().execute(),
|
|
2637
|
+
this.db.selectFrom("targeting_rules").where("enabled", "=", true).selectAll().execute(),
|
|
2638
|
+
this.db.selectFrom("config_variants").selectAll().execute(),
|
|
2639
|
+
this.db.selectFrom("variant_versions").selectAll().execute(),
|
|
2640
|
+
this.db.selectFrom("provider_configs").selectAll().execute()
|
|
2641
|
+
]);
|
|
2642
|
+
const manifestConfigs = {};
|
|
2643
|
+
const configsBySlug = {};
|
|
2644
|
+
for (const config of configs) {
|
|
2645
|
+
manifestConfigs[config.id] = {
|
|
2646
|
+
id: config.id,
|
|
2647
|
+
slug: config.slug,
|
|
2648
|
+
name: config.name ?? null
|
|
2649
|
+
};
|
|
2650
|
+
configsBySlug[config.slug] = config.id;
|
|
2651
|
+
}
|
|
2652
|
+
const manifestEnvironments = {};
|
|
2653
|
+
const environmentsBySlug = {};
|
|
2654
|
+
for (const env of environments) {
|
|
2655
|
+
manifestEnvironments[env.id] = {
|
|
2656
|
+
id: env.id,
|
|
2657
|
+
slug: env.slug,
|
|
2658
|
+
name: env.name,
|
|
2659
|
+
isProd: env.isProd
|
|
2660
|
+
};
|
|
2661
|
+
environmentsBySlug[env.slug] = env.id;
|
|
2662
|
+
}
|
|
2663
|
+
const secretToEnvironment = {};
|
|
2664
|
+
for (const secret of environmentSecrets) secretToEnvironment[secret.keyValue] = secret.environmentId;
|
|
2665
|
+
const configVariantMap = new Map(configVariants.map((cv) => [cv.id, cv]));
|
|
2666
|
+
const providerConfigMap = new Map(providerConfigs.map((pc) => [pc.id, pc]));
|
|
2667
|
+
const versionsByVariant = /* @__PURE__ */ new Map();
|
|
2668
|
+
for (const vv of variantVersions) {
|
|
2669
|
+
const list = versionsByVariant.get(vv.variantId) || [];
|
|
2670
|
+
list.push(vv);
|
|
2671
|
+
versionsByVariant.set(vv.variantId, list);
|
|
2672
|
+
}
|
|
2673
|
+
for (const list of versionsByVariant.values()) list.sort((a, b) => b.version - a.version);
|
|
2674
|
+
const versionById = new Map(variantVersions.map((vv) => [vv.id, vv]));
|
|
2675
|
+
const resolveProvider = (provider) => {
|
|
2676
|
+
if (UUID_REGEX.test(provider)) {
|
|
2677
|
+
const pc = providerConfigMap.get(provider);
|
|
2678
|
+
if (pc) return {
|
|
2679
|
+
providerId: pc.providerId,
|
|
2680
|
+
providerConfigId: pc.id
|
|
2681
|
+
};
|
|
2682
|
+
}
|
|
2683
|
+
return {
|
|
2684
|
+
providerId: provider,
|
|
2685
|
+
providerConfigId: null
|
|
2686
|
+
};
|
|
2687
|
+
};
|
|
2688
|
+
const buildVersion = (vv) => {
|
|
2689
|
+
const { providerId, providerConfigId } = resolveProvider(vv.provider);
|
|
2690
|
+
const jsonData = typeof vv.jsonData === "string" ? JSON.parse(vv.jsonData) : vv.jsonData;
|
|
2691
|
+
return {
|
|
2692
|
+
id: vv.id,
|
|
2693
|
+
variantId: vv.variantId,
|
|
2694
|
+
version: vv.version,
|
|
2695
|
+
provider: providerId,
|
|
2696
|
+
providerConfigId,
|
|
2697
|
+
modelName: vv.modelName,
|
|
2698
|
+
jsonData
|
|
2699
|
+
};
|
|
2700
|
+
};
|
|
2701
|
+
const routingTable = {};
|
|
2702
|
+
for (const rule of targetingRules) {
|
|
2703
|
+
const configVariant = configVariantMap.get(rule.configVariantId);
|
|
2704
|
+
if (!configVariant) continue;
|
|
2705
|
+
const variantId = configVariant.variantId;
|
|
2706
|
+
let resolvedVersionData;
|
|
2707
|
+
if (rule.variantVersionId) resolvedVersionData = versionById.get(rule.variantVersionId);
|
|
2708
|
+
else resolvedVersionData = versionsByVariant.get(variantId)?.[0];
|
|
2709
|
+
if (!resolvedVersionData) continue;
|
|
2710
|
+
let conditions = null;
|
|
2711
|
+
if (rule.conditions) {
|
|
2712
|
+
const conditionsObj = typeof rule.conditions === "string" ? JSON.parse(rule.conditions) : rule.conditions;
|
|
2713
|
+
if (conditionsObj && Object.keys(conditionsObj).length > 0) conditions = conditionsObj;
|
|
2714
|
+
}
|
|
2715
|
+
const manifestRule = {
|
|
2716
|
+
id: rule.id,
|
|
2717
|
+
configVariantId: rule.configVariantId,
|
|
2718
|
+
variantVersionId: rule.variantVersionId,
|
|
2719
|
+
weight: rule.weight,
|
|
2720
|
+
priority: rule.priority,
|
|
2721
|
+
enabled: rule.enabled,
|
|
2722
|
+
conditions,
|
|
2723
|
+
resolvedVersion: buildVersion(resolvedVersionData)
|
|
2724
|
+
};
|
|
2725
|
+
if (!routingTable[rule.configId]) routingTable[rule.configId] = {};
|
|
2726
|
+
if (!routingTable[rule.configId][rule.environmentId]) routingTable[rule.configId][rule.environmentId] = [];
|
|
2727
|
+
routingTable[rule.configId][rule.environmentId].push(manifestRule);
|
|
2728
|
+
}
|
|
2729
|
+
for (const configRules of Object.values(routingTable)) for (const envRules of Object.values(configRules)) envRules.sort((a, b) => {
|
|
2730
|
+
if (b.priority !== a.priority) return b.priority - a.priority;
|
|
2731
|
+
return b.weight - a.weight;
|
|
2732
|
+
});
|
|
2733
|
+
return {
|
|
2734
|
+
version: Date.now(),
|
|
2735
|
+
builtAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2736
|
+
configs: manifestConfigs,
|
|
2737
|
+
configsBySlug,
|
|
2738
|
+
environments: manifestEnvironments,
|
|
2739
|
+
environmentsBySlug,
|
|
2740
|
+
routingTable,
|
|
2741
|
+
secretToEnvironment
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
};
|
|
2745
|
+
|
|
2746
|
+
//#endregion
|
|
2747
|
+
//#region src/manifest/service.ts
|
|
2748
|
+
const MANIFEST_CACHE_KEY = "manifest";
|
|
2749
|
+
const MANIFEST_NAMESPACE = "gateway";
|
|
2750
|
+
const DEFAULT_TTL_MS = 300 * 1e3;
|
|
2751
|
+
const log = logger.child({ module: "ManifestService" });
|
|
2752
|
+
var ManifestService = class {
|
|
2753
|
+
builder;
|
|
2754
|
+
constructor(cache, db, ttlMs = DEFAULT_TTL_MS) {
|
|
2755
|
+
this.cache = cache;
|
|
2756
|
+
this.ttlMs = ttlMs;
|
|
2757
|
+
this.builder = new ManifestBuilder(db);
|
|
2758
|
+
log.debug({ ttlMs }, "ManifestService initialized");
|
|
2759
|
+
}
|
|
2760
|
+
/**
|
|
2761
|
+
* Get the current manifest, building if necessary
|
|
2762
|
+
*/
|
|
2763
|
+
async getManifest() {
|
|
2764
|
+
log.debug("Getting manifest from cache or building");
|
|
2765
|
+
const manifest = await this.cache.getOrSet(MANIFEST_CACHE_KEY, async () => {
|
|
2766
|
+
log.info("Building new manifest");
|
|
2767
|
+
const built = await this.builder.build();
|
|
2768
|
+
log.info({
|
|
2769
|
+
version: built.version,
|
|
2770
|
+
configCount: Object.keys(built.configs).length,
|
|
2771
|
+
environmentCount: Object.keys(built.environments).length
|
|
2772
|
+
}, "Manifest built successfully");
|
|
2773
|
+
return built;
|
|
2774
|
+
}, {
|
|
2775
|
+
namespace: MANIFEST_NAMESPACE,
|
|
2776
|
+
ttl: this.ttlMs
|
|
2777
|
+
});
|
|
2778
|
+
log.debug({ version: manifest.version }, "Manifest retrieved");
|
|
2779
|
+
return manifest;
|
|
2780
|
+
}
|
|
2781
|
+
/**
|
|
2782
|
+
* Force invalidate the manifest (called on mutations)
|
|
2783
|
+
*/
|
|
2784
|
+
async invalidate() {
|
|
2785
|
+
log.info("Invalidating manifest cache");
|
|
2786
|
+
await this.cache.delete(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE);
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* Invalidate and immediately rebuild (atomic refresh)
|
|
2790
|
+
*/
|
|
2791
|
+
async refresh() {
|
|
2792
|
+
log.info("Refreshing manifest (invalidate + rebuild)");
|
|
2793
|
+
await this.invalidate();
|
|
2794
|
+
return this.getManifest();
|
|
2795
|
+
}
|
|
2796
|
+
/**
|
|
2797
|
+
* Get manifest version without fetching full manifest
|
|
2798
|
+
* Useful for checking if manifest is stale
|
|
2799
|
+
*/
|
|
2800
|
+
async getVersion() {
|
|
2801
|
+
const version = (await this.cache.get(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE))?.version ?? null;
|
|
2802
|
+
log.debug({ version }, "Got manifest version");
|
|
2803
|
+
return version;
|
|
2804
|
+
}
|
|
2805
|
+
/**
|
|
2806
|
+
* Check if manifest exists in cache
|
|
2807
|
+
*/
|
|
2808
|
+
async hasManifest() {
|
|
2809
|
+
const exists = await this.cache.has(MANIFEST_CACHE_KEY, MANIFEST_NAMESPACE);
|
|
2810
|
+
log.debug({ exists }, "Checked manifest existence");
|
|
2811
|
+
return exists;
|
|
2812
|
+
}
|
|
2813
|
+
};
|
|
2814
|
+
|
|
2815
|
+
//#endregion
|
|
2816
|
+
//#region src/manifest/router.ts
|
|
2817
|
+
/**
|
|
2818
|
+
* Router for evaluating the gateway manifest and selecting variants
|
|
2819
|
+
*/
|
|
2820
|
+
var ManifestRouter = class {
|
|
2821
|
+
constructor(manifest) {
|
|
2822
|
+
this.manifest = manifest;
|
|
2823
|
+
}
|
|
2824
|
+
/**
|
|
2825
|
+
* Resolve a config identifier (UUID or slug) to config ID
|
|
2826
|
+
*/
|
|
2827
|
+
resolveConfigId(configIdOrSlug) {
|
|
2828
|
+
if (this.manifest.configs[configIdOrSlug]) return configIdOrSlug;
|
|
2829
|
+
return this.manifest.configsBySlug[configIdOrSlug] ?? null;
|
|
2830
|
+
}
|
|
2831
|
+
/**
|
|
2832
|
+
* Resolve environment from secret value
|
|
2833
|
+
*/
|
|
2834
|
+
resolveEnvironmentFromSecret(secretValue) {
|
|
2835
|
+
return this.manifest.secretToEnvironment[secretValue] ?? null;
|
|
2836
|
+
}
|
|
2837
|
+
/**
|
|
2838
|
+
* Get production environment ID
|
|
2839
|
+
*/
|
|
2840
|
+
getProductionEnvironmentId() {
|
|
2841
|
+
for (const env of Object.values(this.manifest.environments)) if (env.isProd) return env.id;
|
|
2842
|
+
return null;
|
|
2843
|
+
}
|
|
2844
|
+
/**
|
|
2845
|
+
* Get environment by ID
|
|
2846
|
+
*/
|
|
2847
|
+
getEnvironment(environmentId) {
|
|
2848
|
+
return this.manifest.environments[environmentId] ?? null;
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Get config by ID
|
|
2852
|
+
*/
|
|
2853
|
+
getConfig(configId) {
|
|
2854
|
+
return this.manifest.configs[configId] ?? null;
|
|
2855
|
+
}
|
|
2856
|
+
/**
|
|
2857
|
+
* Route a request to the appropriate variant version (first match wins)
|
|
2858
|
+
*/
|
|
2859
|
+
route(configIdOrSlug, environmentId, context = {}) {
|
|
2860
|
+
const configId = this.resolveConfigId(configIdOrSlug);
|
|
2861
|
+
if (!configId) return null;
|
|
2862
|
+
const rules = this.manifest.routingTable[configId]?.[environmentId];
|
|
2863
|
+
if (!rules || rules.length === 0) return null;
|
|
2864
|
+
const evalContext = {
|
|
2865
|
+
...context,
|
|
2866
|
+
timestamp: context.timestamp ?? Date.now()
|
|
2867
|
+
};
|
|
2868
|
+
for (const rule of rules) {
|
|
2869
|
+
if (!rule.enabled) continue;
|
|
2870
|
+
if (rule.conditions) try {
|
|
2871
|
+
if (!jsonLogic.apply(rule.conditions, evalContext)) continue;
|
|
2872
|
+
} catch (error) {
|
|
2873
|
+
console.warn(`JSONLogic evaluation error for rule ${rule.id}:`, error);
|
|
2874
|
+
continue;
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
configId,
|
|
2878
|
+
environmentId,
|
|
2879
|
+
variantId: rule.resolvedVersion.variantId,
|
|
2880
|
+
version: rule.resolvedVersion,
|
|
2881
|
+
rule
|
|
2882
|
+
};
|
|
2883
|
+
}
|
|
2884
|
+
return null;
|
|
2885
|
+
}
|
|
2886
|
+
/**
|
|
2887
|
+
* Route with weighted random selection among matching rules of same priority
|
|
2888
|
+
*/
|
|
2889
|
+
routeWithWeights(configIdOrSlug, environmentId, context = {}) {
|
|
2890
|
+
const configId = this.resolveConfigId(configIdOrSlug);
|
|
2891
|
+
if (!configId) return null;
|
|
2892
|
+
const rules = this.manifest.routingTable[configId]?.[environmentId];
|
|
2893
|
+
if (!rules || rules.length === 0) return null;
|
|
2894
|
+
const evalContext = {
|
|
2895
|
+
...context,
|
|
2896
|
+
timestamp: context.timestamp ?? Date.now()
|
|
2897
|
+
};
|
|
2898
|
+
const rulesByPriority = /* @__PURE__ */ new Map();
|
|
2899
|
+
for (const rule$1 of rules) {
|
|
2900
|
+
if (!rule$1.enabled) continue;
|
|
2901
|
+
if (rule$1.conditions) try {
|
|
2902
|
+
if (!jsonLogic.apply(rule$1.conditions, evalContext)) continue;
|
|
2903
|
+
} catch {
|
|
2904
|
+
continue;
|
|
2905
|
+
}
|
|
2906
|
+
const list = rulesByPriority.get(rule$1.priority) || [];
|
|
2907
|
+
list.push(rule$1);
|
|
2908
|
+
rulesByPriority.set(rule$1.priority, list);
|
|
2909
|
+
}
|
|
2910
|
+
const priorities = Array.from(rulesByPriority.keys()).sort((a, b) => b - a);
|
|
2911
|
+
if (priorities.length === 0) return null;
|
|
2912
|
+
const topPriorityRules = rulesByPriority.get(priorities[0]);
|
|
2913
|
+
if (topPriorityRules.length === 1) {
|
|
2914
|
+
const rule$1 = topPriorityRules[0];
|
|
2915
|
+
return {
|
|
2916
|
+
configId,
|
|
2917
|
+
environmentId,
|
|
2918
|
+
variantId: rule$1.resolvedVersion.variantId,
|
|
2919
|
+
version: rule$1.resolvedVersion,
|
|
2920
|
+
rule: rule$1
|
|
2921
|
+
};
|
|
2922
|
+
}
|
|
2923
|
+
const totalWeight = topPriorityRules.reduce((sum, r) => sum + r.weight, 0);
|
|
2924
|
+
if (totalWeight === 0) return null;
|
|
2925
|
+
let random = Math.random() * totalWeight;
|
|
2926
|
+
for (const rule$1 of topPriorityRules) {
|
|
2927
|
+
random -= rule$1.weight;
|
|
2928
|
+
if (random <= 0) return {
|
|
2929
|
+
configId,
|
|
2930
|
+
environmentId,
|
|
2931
|
+
variantId: rule$1.resolvedVersion.variantId,
|
|
2932
|
+
version: rule$1.resolvedVersion,
|
|
2933
|
+
rule: rule$1
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2936
|
+
const rule = topPriorityRules[0];
|
|
2937
|
+
return {
|
|
2938
|
+
configId,
|
|
2939
|
+
environmentId,
|
|
2940
|
+
variantId: rule.resolvedVersion.variantId,
|
|
2941
|
+
version: rule.resolvedVersion,
|
|
2942
|
+
rule
|
|
2943
|
+
};
|
|
2944
|
+
}
|
|
2945
|
+
};
|
|
2946
|
+
|
|
2947
|
+
//#endregion
|
|
2948
|
+
export { CacheService, FileCacheBackend, MS, ManifestBuilder, ManifestRouter, ManifestService, MemoryCacheBackend, ModelsDevPricingProvider, SCHEMA_METADATA, SupportedProviders, calculateCost, chatCompletionCreateParamsBaseSchema, configVariantsSchema, configsSchema, createDataLayer, createDatabase, createDatabaseFromConnection, createLLMRequestsDataLayer, createNeonDialect, createProviderConfigsDataLayer, createWorkspaceSettingsDataLayer, detectDatabaseType, dollarsToMicroDollars, environmentSecretsSchema, environmentsSchema, executeWithSchema, formatCost, gateway, generateId, getAuthClientOptions, getDefaultPricingProvider, getMigrations, llmRequestsSchema, llmopsConfigSchema, logger, matchType, microDollarsToDollars, parsePartialTableData, parseTableData, providerConfigsSchema, runAutoMigrations, schemas, targetingRulesSchema, validateLLMOpsConfig, validatePartialTableData, validateTableData, variantJsonDataSchema, variantVersionsSchema, variantsSchema, workspaceSettingsSchema };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llmops/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0-beta.1",
|
|
4
4
|
"description": "Core LLMOps functionality and utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,12 +51,14 @@
|
|
|
51
51
|
"@neondatabase/serverless": "^1.0.2",
|
|
52
52
|
"better-auth": "^1.4.10",
|
|
53
53
|
"hono": "^4.10.7",
|
|
54
|
+
"json-logic-js": "^2.0.5",
|
|
54
55
|
"kysely": "^0.28.8",
|
|
55
56
|
"kysely-neon": "^2.0.2",
|
|
56
57
|
"pino": "^10.1.0",
|
|
57
|
-
"@llmops/gateway": "^0.
|
|
58
|
+
"@llmops/gateway": "^0.3.0-beta.1"
|
|
58
59
|
},
|
|
59
60
|
"devDependencies": {
|
|
61
|
+
"@types/json-logic-js": "^2.0.8",
|
|
60
62
|
"@types/ws": "^8.18.1"
|
|
61
63
|
},
|
|
62
64
|
"scripts": {
|