@axiom-lattice/pg-stores 3.1.21 → 3.2.0

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,8 +1,8 @@
1
1
  // src/index.ts
2
- import { Pool as Pool30 } from "pg";
2
+ import { Pool as Pool31 } from "pg";
3
3
 
4
4
  // src/createPgStoreConfig.ts
5
- import { Pool as Pool27 } from "pg";
5
+ import { Pool as Pool28 } from "pg";
6
6
 
7
7
  // src/migrations/migration.ts
8
8
  var MigrationManager = class {
@@ -9961,6 +9961,116 @@ var createUserPushSubscriptions = {
9961
9961
  }
9962
9962
  };
9963
9963
 
9964
+ // src/migrations/model_provider_migrations.ts
9965
+ var createModelProvidersTable = {
9966
+ version: 181,
9967
+ name: "create_model_providers_table",
9968
+ up: async (client) => {
9969
+ await client.query(`
9970
+ CREATE TABLE IF NOT EXISTS lattice_model_providers (
9971
+ id VARCHAR(255) NOT NULL,
9972
+ tenant_id VARCHAR(255) NOT NULL,
9973
+ name VARCHAR(255) NOT NULL,
9974
+ display_name VARCHAR(255),
9975
+ protocol VARCHAR(64) NOT NULL,
9976
+ api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions',
9977
+ llm_provider VARCHAR(64) NOT NULL,
9978
+ base_url TEXT NOT NULL,
9979
+ api_key_enc TEXT,
9980
+ api_key_hint VARCHAR(16),
9981
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
9982
+ status VARCHAR(32) NOT NULL DEFAULT 'unknown',
9983
+ last_error VARCHAR(255),
9984
+ last_discovered_at TIMESTAMP,
9985
+ created_by VARCHAR(255),
9986
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
9987
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
9988
+
9989
+ PRIMARY KEY (tenant_id, id),
9990
+ CONSTRAINT uk_lattice_model_providers_tenant_name UNIQUE (tenant_id, name)
9991
+ )
9992
+ `);
9993
+ await client.query(`
9994
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_providers_tenant_id
9995
+ ON lattice_model_providers(tenant_id)
9996
+ `);
9997
+ },
9998
+ down: async (client) => {
9999
+ await client.query("DROP INDEX IF EXISTS idx_lattice_model_providers_tenant_id");
10000
+ await client.query("DROP TABLE IF EXISTS lattice_model_providers");
10001
+ }
10002
+ };
10003
+ var createModelProviderModelsTable = {
10004
+ version: 182,
10005
+ name: "create_model_provider_models_table",
10006
+ up: async (client) => {
10007
+ await client.query(`
10008
+ CREATE TABLE IF NOT EXISTS lattice_model_provider_models (
10009
+ id VARCHAR(255) NOT NULL,
10010
+ tenant_id VARCHAR(255) NOT NULL,
10011
+ provider_id VARCHAR(255) NOT NULL,
10012
+ model_id VARCHAR(512) NOT NULL,
10013
+ display_name VARCHAR(255),
10014
+ owned_by VARCHAR(255),
10015
+ upstream_created TIMESTAMP,
10016
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
10017
+ raw JSONB,
10018
+ discovered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
10019
+
10020
+ PRIMARY KEY (tenant_id, provider_id, model_id)
10021
+ )
10022
+ `);
10023
+ await client.query(`
10024
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_id
10025
+ ON lattice_model_provider_models(tenant_id)
10026
+ `);
10027
+ await client.query(`
10028
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_model
10029
+ ON lattice_model_provider_models(tenant_id, model_id)
10030
+ `);
10031
+ await client.query(`
10032
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_provider
10033
+ ON lattice_model_provider_models(tenant_id, provider_id)
10034
+ `);
10035
+ },
10036
+ down: async (client) => {
10037
+ await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_provider");
10038
+ await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_model");
10039
+ await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_id");
10040
+ await client.query("DROP TABLE IF EXISTS lattice_model_provider_models");
10041
+ }
10042
+ };
10043
+ var addModelProviderApiStyle = {
10044
+ version: 183,
10045
+ name: "add_model_provider_api_style",
10046
+ up: async (client) => {
10047
+ await client.query(`
10048
+ ALTER TABLE lattice_model_providers
10049
+ ADD COLUMN IF NOT EXISTS api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions'
10050
+ `);
10051
+ },
10052
+ down: async (client) => {
10053
+ await client.query(`
10054
+ ALTER TABLE lattice_model_providers DROP COLUMN IF EXISTS api_style
10055
+ `);
10056
+ }
10057
+ };
10058
+ var addModelProviderModelStale = {
10059
+ version: 184,
10060
+ name: "add_model_provider_model_stale",
10061
+ up: async (client) => {
10062
+ await client.query(`
10063
+ ALTER TABLE lattice_model_provider_models
10064
+ ADD COLUMN IF NOT EXISTS stale BOOLEAN NOT NULL DEFAULT FALSE
10065
+ `);
10066
+ },
10067
+ down: async (client) => {
10068
+ await client.query(`
10069
+ ALTER TABLE lattice_model_provider_models DROP COLUMN IF EXISTS stale
10070
+ `);
10071
+ }
10072
+ };
10073
+
9964
10074
  // src/stores/PostgreSQLCapabilityBundleStore.ts
9965
10075
  import { randomUUID as randomUUID3 } from "crypto";
9966
10076
  import { Pool as Pool26 } from "pg";
@@ -10922,9 +11032,513 @@ var PostgreSQLUserPushSubscriptionStore = class {
10922
11032
  }
10923
11033
  };
10924
11034
 
11035
+ // src/stores/PostgreSQLModelProviderStore.ts
11036
+ import { Pool as Pool27 } from "pg";
11037
+ import { encrypt as encrypt6, decrypt as decrypt6 } from "@axiom-lattice/core";
11038
+ var PostgreSQLModelProviderStore = class {
11039
+ constructor(options) {
11040
+ this.initialized = false;
11041
+ this.ownsPool = true;
11042
+ this.initPromise = null;
11043
+ if (options.pool) {
11044
+ this.pool = options.pool;
11045
+ this.ownsPool = false;
11046
+ this.initialized = true;
11047
+ return;
11048
+ }
11049
+ if (typeof options.poolConfig === "string") {
11050
+ this.pool = new Pool27({ connectionString: options.poolConfig });
11051
+ } else if (options.poolConfig) {
11052
+ this.pool = new Pool27(options.poolConfig);
11053
+ } else {
11054
+ throw new Error("Either pool or poolConfig must be provided");
11055
+ }
11056
+ this.migrationManager = new MigrationManager(this.pool);
11057
+ this.migrationManager.register(createModelProvidersTable);
11058
+ this.migrationManager.register(createModelProviderModelsTable);
11059
+ if (options.autoMigrate !== false) {
11060
+ this.initialize().catch((error) => {
11061
+ console.error("Failed to initialize PostgreSQLModelProviderStore:", error);
11062
+ throw error;
11063
+ });
11064
+ }
11065
+ }
11066
+ /**
11067
+ * Initialize the store and run migrations
11068
+ * Uses a promise-based lock to prevent concurrent initialization
11069
+ */
11070
+ async initialize() {
11071
+ if (this.initialized) {
11072
+ return;
11073
+ }
11074
+ if (this.initPromise) {
11075
+ return this.initPromise;
11076
+ }
11077
+ this.initPromise = (async () => {
11078
+ try {
11079
+ await this.migrationManager.migrate();
11080
+ this.initialized = true;
11081
+ } finally {
11082
+ this.initPromise = null;
11083
+ }
11084
+ })();
11085
+ return this.initPromise;
11086
+ }
11087
+ /**
11088
+ * Get all provider configurations for a tenant
11089
+ */
11090
+ async listProviders(tenantId) {
11091
+ await this.ensureInitialized();
11092
+ const result = await this.pool.query(
11093
+ `
11094
+ SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
11095
+ api_key_enc, api_key_hint, enabled, status, last_error,
11096
+ last_discovered_at, created_by, created_at, updated_at
11097
+ FROM lattice_model_providers
11098
+ WHERE tenant_id = $1
11099
+ ORDER BY created_at DESC
11100
+ `,
11101
+ [tenantId]
11102
+ );
11103
+ return result.rows.map((row) => this.mapRowToEntry(row));
11104
+ }
11105
+ /**
11106
+ * Get a provider configuration by ID
11107
+ */
11108
+ async getProviderById(tenantId, id) {
11109
+ await this.ensureInitialized();
11110
+ const result = await this.pool.query(
11111
+ `
11112
+ SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
11113
+ api_key_enc, api_key_hint, enabled, status, last_error,
11114
+ last_discovered_at, created_by, created_at, updated_at
11115
+ FROM lattice_model_providers
11116
+ WHERE tenant_id = $1 AND id = $2
11117
+ `,
11118
+ [tenantId, id]
11119
+ );
11120
+ if (result.rows.length === 0) {
11121
+ return null;
11122
+ }
11123
+ return this.mapRowToEntry(result.rows[0]);
11124
+ }
11125
+ /**
11126
+ * Get a provider configuration by business name
11127
+ */
11128
+ async getProviderByName(tenantId, name) {
11129
+ await this.ensureInitialized();
11130
+ const result = await this.pool.query(
11131
+ `
11132
+ SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
11133
+ api_key_enc, api_key_hint, enabled, status, last_error,
11134
+ last_discovered_at, created_by, created_at, updated_at
11135
+ FROM lattice_model_providers
11136
+ WHERE tenant_id = $1 AND name = $2
11137
+ `,
11138
+ [tenantId, name]
11139
+ );
11140
+ if (result.rows.length === 0) {
11141
+ return null;
11142
+ }
11143
+ return this.mapRowToEntry(result.rows[0]);
11144
+ }
11145
+ /**
11146
+ * Create a new provider configuration
11147
+ */
11148
+ async createProvider(tenantId, id, data) {
11149
+ await this.ensureInitialized();
11150
+ const now = /* @__PURE__ */ new Date();
11151
+ const nowString = now.toISOString();
11152
+ const apiKeyEnc = data.apiKey ? encrypt6(data.apiKey) : null;
11153
+ const apiKeyHint = data.apiKey ? data.apiKey.slice(-4) : null;
11154
+ await this.pool.query(
11155
+ `
11156
+ INSERT INTO lattice_model_providers (
11157
+ id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
11158
+ api_key_enc, api_key_hint, enabled, status, created_at, updated_at
11159
+ )
11160
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'unknown', $12::timestamp, $13::timestamp)
11161
+ ON CONFLICT (tenant_id, id) DO UPDATE SET
11162
+ name = EXCLUDED.name,
11163
+ display_name = EXCLUDED.display_name,
11164
+ protocol = EXCLUDED.protocol,
11165
+ api_style = EXCLUDED.api_style,
11166
+ llm_provider = EXCLUDED.llm_provider,
11167
+ base_url = EXCLUDED.base_url,
11168
+ api_key_enc = EXCLUDED.api_key_enc,
11169
+ api_key_hint = EXCLUDED.api_key_hint,
11170
+ enabled = EXCLUDED.enabled,
11171
+ updated_at = EXCLUDED.updated_at
11172
+ `,
11173
+ [
11174
+ id,
11175
+ tenantId,
11176
+ data.name,
11177
+ data.displayName || null,
11178
+ data.protocol,
11179
+ data.apiStyle || "chat-completions",
11180
+ // Phase 1 pins the LLM provider: the UI does not offer a choice, and
11181
+ // some initChatModel branches hardcode their own baseURL and would
11182
+ // silently ignore the tenant's (see ADR-138 decision 7b).
11183
+ "openai",
11184
+ data.baseURL,
11185
+ apiKeyEnc,
11186
+ apiKeyHint,
11187
+ data.enabled !== false,
11188
+ nowString,
11189
+ nowString
11190
+ ]
11191
+ );
11192
+ const created = await this.getProviderById(tenantId, id);
11193
+ if (!created) {
11194
+ throw new Error("Failed to create model provider");
11195
+ }
11196
+ return created;
11197
+ }
11198
+ /**
11199
+ * Update an existing provider configuration.
11200
+ * An omitted or empty `apiKey` leaves the stored key untouched.
11201
+ */
11202
+ async updateProvider(tenantId, id, updates) {
11203
+ await this.ensureInitialized();
11204
+ const existing = await this.getProviderById(tenantId, id);
11205
+ if (!existing) {
11206
+ return null;
11207
+ }
11208
+ const updateData = {};
11209
+ if (updates.name !== void 0) {
11210
+ updateData.name = updates.name;
11211
+ }
11212
+ if (updates.displayName !== void 0) {
11213
+ updateData.display_name = updates.displayName || null;
11214
+ }
11215
+ if (updates.protocol !== void 0) {
11216
+ updateData.protocol = updates.protocol;
11217
+ }
11218
+ if (updates.apiStyle !== void 0) {
11219
+ updateData.api_style = updates.apiStyle;
11220
+ }
11221
+ if (updates.baseURL !== void 0) {
11222
+ updateData.base_url = updates.baseURL;
11223
+ }
11224
+ if (updates.clearApiKey === true) {
11225
+ updateData.api_key_enc = null;
11226
+ updateData.api_key_hint = null;
11227
+ } else if (updates.apiKey) {
11228
+ updateData.api_key_enc = encrypt6(updates.apiKey);
11229
+ updateData.api_key_hint = updates.apiKey.slice(-4);
11230
+ }
11231
+ if (updates.enabled !== void 0) {
11232
+ updateData.enabled = updates.enabled;
11233
+ }
11234
+ if (Object.keys(updateData).length === 0) {
11235
+ return existing;
11236
+ }
11237
+ updateData.updated_at = (/* @__PURE__ */ new Date()).toISOString();
11238
+ const fields = Object.keys(updateData);
11239
+ const values = Object.values(updateData);
11240
+ values.push(tenantId);
11241
+ values.push(id);
11242
+ const setClauses = fields.map(
11243
+ (field, index) => field === "updated_at" ? `${field} = $${index + 1}::timestamp` : `${field} = $${index + 1}`
11244
+ );
11245
+ const whereTenantIndex = fields.length + 1;
11246
+ const whereIdIndex = fields.length + 2;
11247
+ const sql = `
11248
+ UPDATE lattice_model_providers
11249
+ SET ${setClauses.join(", ")}
11250
+ WHERE tenant_id = $${whereTenantIndex} AND id = $${whereIdIndex}
11251
+ `;
11252
+ await this.pool.query(sql, values);
11253
+ return await this.getProviderById(tenantId, id);
11254
+ }
11255
+ /**
11256
+ * Delete a provider configuration and its model snapshot
11257
+ */
11258
+ async deleteProvider(tenantId, id) {
11259
+ await this.ensureInitialized();
11260
+ await this.deleteModelsByProvider(tenantId, id);
11261
+ const result = await this.pool.query(
11262
+ `
11263
+ DELETE FROM lattice_model_providers
11264
+ WHERE tenant_id = $1 AND id = $2
11265
+ `,
11266
+ [tenantId, id]
11267
+ );
11268
+ return result.rowCount !== null && result.rowCount > 0;
11269
+ }
11270
+ /**
11271
+ * Record the outcome of a discovery attempt
11272
+ */
11273
+ async markProviderStatus(tenantId, id, status, lastError, discoveredAt) {
11274
+ await this.ensureInitialized();
11275
+ await this.pool.query(
11276
+ `
11277
+ UPDATE lattice_model_providers
11278
+ SET status = $3,
11279
+ last_error = $4,
11280
+ last_discovered_at = CASE
11281
+ WHEN $5::timestamp IS NULL THEN last_discovered_at
11282
+ ELSE $5::timestamp
11283
+ END,
11284
+ updated_at = $6::timestamp
11285
+ WHERE tenant_id = $1 AND id = $2
11286
+ `,
11287
+ [
11288
+ tenantId,
11289
+ id,
11290
+ status,
11291
+ status === "ok" ? null : lastError || null,
11292
+ discoveredAt ? discoveredAt.toISOString() : null,
11293
+ (/* @__PURE__ */ new Date()).toISOString()
11294
+ ]
11295
+ );
11296
+ }
11297
+ /**
11298
+ * List snapshot models for a tenant
11299
+ */
11300
+ async listModels(tenantId, providerId) {
11301
+ await this.ensureInitialized();
11302
+ const result = providerId ? await this.pool.query(
11303
+ `
11304
+ SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
11305
+ upstream_created, enabled, stale, discovered_at
11306
+ FROM lattice_model_provider_models
11307
+ WHERE tenant_id = $1 AND provider_id = $2
11308
+ ORDER BY model_id ASC
11309
+ `,
11310
+ [tenantId, providerId]
11311
+ ) : await this.pool.query(
11312
+ `
11313
+ SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
11314
+ upstream_created, enabled, discovered_at
11315
+ FROM lattice_model_provider_models
11316
+ WHERE tenant_id = $1
11317
+ ORDER BY model_id ASC
11318
+ `,
11319
+ [tenantId]
11320
+ );
11321
+ return result.rows.map((row) => this.mapRowToModel(row));
11322
+ }
11323
+ /**
11324
+ * Get a single snapshot model.
11325
+ * Keyed by provider as well as model id, since two providers in the same
11326
+ * tenant may expose the same upstream model id.
11327
+ */
11328
+ async getModel(tenantId, providerId, modelId) {
11329
+ await this.ensureInitialized();
11330
+ const result = await this.pool.query(
11331
+ `
11332
+ SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
11333
+ upstream_created, enabled, discovered_at
11334
+ FROM lattice_model_provider_models
11335
+ WHERE tenant_id = $1 AND provider_id = $2 AND model_id = $3
11336
+ `,
11337
+ [tenantId, providerId, modelId]
11338
+ );
11339
+ if (result.rows.length === 0) {
11340
+ return null;
11341
+ }
11342
+ return this.mapRowToModel(result.rows[0]);
11343
+ }
11344
+ /**
11345
+ * Replace a provider's model snapshot with the result of one discovery.
11346
+ *
11347
+ * Runs in a single transaction. Model ids are scoped per provider, so the
11348
+ * same upstream id may appear under several providers in one tenant — each
11349
+ * is an independent model.
11350
+ *
11351
+ * Models the upstream no longer offers are deleted; models that survive are
11352
+ * upserted *without* touching `enabled`, so the tenant's enable/disable
11353
+ * choice survives a re-discovery. New rows take the column default (enabled).
11354
+ */
11355
+ async replaceProviderModels(tenantId, providerId, models) {
11356
+ await this.ensureInitialized();
11357
+ const client = await this.pool.connect();
11358
+ try {
11359
+ await client.query("BEGIN");
11360
+ const current = await client.query(
11361
+ `
11362
+ SELECT model_id, enabled FROM lattice_model_provider_models
11363
+ WHERE tenant_id = $1 AND provider_id = $2
11364
+ `,
11365
+ [tenantId, providerId]
11366
+ );
11367
+ const currentEnabled = new Map(
11368
+ current.rows.map((row) => [row.model_id, row.enabled])
11369
+ );
11370
+ const deduped = /* @__PURE__ */ new Map();
11371
+ for (const model of models) {
11372
+ deduped.set(model.id, model);
11373
+ }
11374
+ const rows = [...deduped.values()];
11375
+ const effectiveEnabled = rows.map(
11376
+ (model) => model.enabled ?? currentEnabled.get(model.id) ?? true
11377
+ );
11378
+ await client.query(
11379
+ `
11380
+ DELETE FROM lattice_model_provider_models
11381
+ WHERE tenant_id = $1
11382
+ AND provider_id = $2
11383
+ AND model_id <> ALL($3::text[])
11384
+ `,
11385
+ [tenantId, providerId, rows.map((model) => model.id)]
11386
+ );
11387
+ if (models.length > 0) {
11388
+ await client.query(
11389
+ `
11390
+ INSERT INTO lattice_model_provider_models (
11391
+ id, tenant_id, provider_id, model_id, display_name, owned_by,
11392
+ upstream_created, raw, enabled, stale, discovered_at
11393
+ )
11394
+ SELECT * FROM unnest(
11395
+ $1::text[], $2::text[], $3::text[], $4::text[], $5::text[],
11396
+ $6::text[], $7::timestamp[], $8::jsonb[], $9::boolean[], $10::boolean[], $11::timestamp[]
11397
+ ) AS t(
11398
+ id, tenant_id, provider_id, model_id, display_name, owned_by,
11399
+ upstream_created, raw, enabled, stale, discovered_at
11400
+ )
11401
+ ON CONFLICT (tenant_id, provider_id, model_id) DO UPDATE SET
11402
+ display_name = EXCLUDED.display_name,
11403
+ owned_by = EXCLUDED.owned_by,
11404
+ upstream_created = EXCLUDED.upstream_created,
11405
+ raw = EXCLUDED.raw,
11406
+ enabled = EXCLUDED.enabled,
11407
+ stale = EXCLUDED.stale,
11408
+ discovered_at = EXCLUDED.discovered_at
11409
+ `,
11410
+ [
11411
+ rows.map((model) => `${providerId}:${model.id}`),
11412
+ rows.map(() => tenantId),
11413
+ rows.map(() => providerId),
11414
+ rows.map((model) => model.id),
11415
+ rows.map((model) => model.displayName ?? null),
11416
+ rows.map((model) => model.ownedBy ?? null),
11417
+ rows.map(
11418
+ (model) => model.createdAt ? new Date(model.createdAt).toISOString() : null
11419
+ ),
11420
+ rows.map(
11421
+ (model) => model.raw === void 0 ? null : JSON.stringify(model.raw)
11422
+ ),
11423
+ effectiveEnabled,
11424
+ rows.map((model) => model.stale ?? false),
11425
+ rows.map(() => (/* @__PURE__ */ new Date()).toISOString())
11426
+ ]
11427
+ );
11428
+ }
11429
+ await client.query("COMMIT");
11430
+ } catch (error) {
11431
+ await client.query("ROLLBACK");
11432
+ throw error;
11433
+ } finally {
11434
+ client.release();
11435
+ }
11436
+ return models.map((model) => model.id);
11437
+ }
11438
+ /**
11439
+ * Enable or disable a single snapshot model
11440
+ */
11441
+ async setModelEnabled(tenantId, providerId, modelId, enabled) {
11442
+ await this.ensureInitialized();
11443
+ const result = await this.pool.query(
11444
+ `
11445
+ UPDATE lattice_model_provider_models
11446
+ SET enabled = $4
11447
+ WHERE tenant_id = $1 AND provider_id = $2 AND model_id = $3
11448
+ `,
11449
+ [tenantId, providerId, modelId, enabled]
11450
+ );
11451
+ if (result.rowCount === null || result.rowCount === 0) {
11452
+ return null;
11453
+ }
11454
+ return await this.getModel(tenantId, providerId, modelId);
11455
+ }
11456
+ /**
11457
+ * Delete every snapshot model belonging to a provider
11458
+ */
11459
+ async deleteModelsByProvider(tenantId, providerId) {
11460
+ await this.ensureInitialized();
11461
+ const result = await this.pool.query(
11462
+ `
11463
+ DELETE FROM lattice_model_provider_models
11464
+ WHERE tenant_id = $1 AND provider_id = $2
11465
+ `,
11466
+ [tenantId, providerId]
11467
+ );
11468
+ return result.rowCount ?? 0;
11469
+ }
11470
+ /**
11471
+ * Dispose resources and close the connection pool
11472
+ */
11473
+ async dispose() {
11474
+ if (this.ownsPool && this.pool) {
11475
+ await this.pool.end();
11476
+ }
11477
+ }
11478
+ /**
11479
+ * Ensure store is initialized
11480
+ */
11481
+ async ensureInitialized() {
11482
+ if (!this.initialized) {
11483
+ await this.initialize();
11484
+ }
11485
+ }
11486
+ /**
11487
+ * Map provider row to ModelProviderEntry
11488
+ * Automatically decrypts the API key if present
11489
+ */
11490
+ mapRowToEntry(row) {
11491
+ let apiKey;
11492
+ if (row.api_key_enc) {
11493
+ try {
11494
+ apiKey = decrypt6(row.api_key_enc);
11495
+ } catch (error) {
11496
+ console.error("Failed to decrypt model provider API key:", error);
11497
+ throw new Error("Failed to decrypt model provider API key");
11498
+ }
11499
+ }
11500
+ return {
11501
+ id: row.id,
11502
+ tenantId: row.tenant_id,
11503
+ name: row.name,
11504
+ displayName: row.display_name || void 0,
11505
+ protocol: row.protocol,
11506
+ apiStyle: row.api_style,
11507
+ llmProvider: row.llm_provider,
11508
+ baseURL: row.base_url,
11509
+ apiKey,
11510
+ apiKeyHint: row.api_key_hint || void 0,
11511
+ enabled: row.enabled,
11512
+ status: row.status,
11513
+ lastError: row.last_error || void 0,
11514
+ lastDiscoveredAt: row.last_discovered_at || void 0,
11515
+ createdBy: row.created_by || void 0,
11516
+ createdAt: row.created_at,
11517
+ updatedAt: row.updated_at
11518
+ };
11519
+ }
11520
+ /**
11521
+ * Map model row to ModelProviderModelEntry
11522
+ */
11523
+ mapRowToModel(row) {
11524
+ return {
11525
+ id: row.id,
11526
+ tenantId: row.tenant_id,
11527
+ providerId: row.provider_id,
11528
+ modelId: row.model_id,
11529
+ displayName: row.display_name || void 0,
11530
+ ownedBy: row.owned_by || void 0,
11531
+ upstreamCreatedAt: row.upstream_created || void 0,
11532
+ enabled: row.enabled,
11533
+ stale: row.stale,
11534
+ discoveredAt: row.discovered_at
11535
+ };
11536
+ }
11537
+ };
11538
+
10925
11539
  // src/createPgStoreConfig.ts
10926
11540
  async function createPgStoreConfig(connectionString) {
10927
- const pool = new Pool27({ connectionString });
11541
+ const pool = new Pool28({ connectionString });
10928
11542
  const mm = new MigrationManager(pool);
10929
11543
  mm.register(createThreadsTable);
10930
11544
  mm.register(createScheduledTasksTable);
@@ -10992,6 +11606,10 @@ async function createPgStoreConfig(connectionString) {
10992
11606
  mm.register(createProjectRoomReadStates);
10993
11607
  mm.register(createUserPushSubscriptions);
10994
11608
  mm.register(addProjectRoomMessageRetentionIndex);
11609
+ mm.register(createModelProvidersTable);
11610
+ mm.register(createModelProviderModelsTable);
11611
+ mm.register(addModelProviderApiStyle);
11612
+ mm.register(addModelProviderModelStale);
10995
11613
  await mm.migrate();
10996
11614
  const checkpoint = PostgresSaver.fromConnString(connectionString);
10997
11615
  checkpoint.setup().catch((err) => {
@@ -11013,6 +11631,7 @@ async function createPgStoreConfig(connectionString) {
11013
11631
  connection: new PostgreSQLConnectionStore(pool),
11014
11632
  metrics: new PostgreSQLMetricsServerConfigStore(opts),
11015
11633
  mcp: new PostgreSQLMcpServerConfigStore(opts),
11634
+ modelProvider: new PostgreSQLModelProviderStore(opts),
11016
11635
  assistant: new PostgreSQLAssistantStore(opts),
11017
11636
  workflowTracking: new PostgreSQLWorkflowTrackingStore(opts),
11018
11637
  threadMessageQueue: new ThreadMessageQueueStore(opts),
@@ -11038,7 +11657,7 @@ async function createPgStoreConfig(connectionString) {
11038
11657
  }
11039
11658
 
11040
11659
  // src/stores/PostgreSQLSkillStore.ts
11041
- import { Pool as Pool28 } from "pg";
11660
+ import { Pool as Pool29 } from "pg";
11042
11661
  var PostgreSQLSkillStore = class {
11043
11662
  constructor(options) {
11044
11663
  this.initialized = false;
@@ -11051,9 +11670,9 @@ var PostgreSQLSkillStore = class {
11051
11670
  return;
11052
11671
  }
11053
11672
  if (typeof options.poolConfig === "string") {
11054
- this.pool = new Pool28({ connectionString: options.poolConfig });
11673
+ this.pool = new Pool29({ connectionString: options.poolConfig });
11055
11674
  } else if (options.poolConfig) {
11056
- this.pool = new Pool28(options.poolConfig);
11675
+ this.pool = new Pool29(options.poolConfig);
11057
11676
  } else {
11058
11677
  throw new Error("Either pool or poolConfig must be provided");
11059
11678
  }
@@ -11354,7 +11973,7 @@ var PostgreSQLSkillStore = class {
11354
11973
  };
11355
11974
 
11356
11975
  // src/stores/ChannelIdentityMappingStore.ts
11357
- import { Pool as Pool29 } from "pg";
11976
+ import { Pool as Pool30 } from "pg";
11358
11977
  var ChannelIdentityMappingStore = class {
11359
11978
  constructor(options) {
11360
11979
  this.initialized = false;
@@ -11366,7 +11985,7 @@ var ChannelIdentityMappingStore = class {
11366
11985
  this.initialized = true;
11367
11986
  return;
11368
11987
  }
11369
- this.pool = typeof options.poolConfig === "string" ? new Pool29({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool29(options.poolConfig) : (() => {
11988
+ this.pool = typeof options.poolConfig === "string" ? new Pool30({ connectionString: options.poolConfig }) : options.poolConfig ? new Pool30(options.poolConfig) : (() => {
11370
11989
  throw new Error("Either pool or poolConfig must be provided");
11371
11990
  })();
11372
11991
  this.migrationManager = new MigrationManager(this.pool);
@@ -11592,7 +12211,7 @@ export {
11592
12211
  MenuStore,
11593
12212
  MigrationManager,
11594
12213
  PGVectorStoreProvider,
11595
- Pool30 as Pool,
12214
+ Pool31 as Pool,
11596
12215
  PostgreSQLA2AApiKeyStore,
11597
12216
  PostgreSQLAgentWebAppStore,
11598
12217
  PostgreSQLAssistantStore,
@@ -11604,6 +12223,7 @@ export {
11604
12223
  PostgreSQLEvalStore,
11605
12224
  PostgreSQLMcpServerConfigStore,
11606
12225
  PostgreSQLMetricsServerConfigStore,
12226
+ PostgreSQLModelProviderStore,
11607
12227
  PostgreSQLOpenAuditStore,
11608
12228
  PostgreSQLProjectBotMembershipStore,
11609
12229
  PostgreSQLProjectMembershipStore,