@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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +59 -0
- package/dist/index.d.mts +125 -2
- package/dist/index.d.ts +125 -2
- package/dist/index.js +630 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +629 -9
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/scripts/check-migration-versions.mjs +6 -1
- package/src/createPgStoreConfig.ts +12 -0
- package/src/index.ts +2 -0
- package/src/migrations/model_provider_migrations.ts +157 -0
- package/src/stores/PostgreSQLModelProviderStore.ts +731 -0
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ __export(index_exports, {
|
|
|
37
37
|
MenuStore: () => MenuStore,
|
|
38
38
|
MigrationManager: () => MigrationManager,
|
|
39
39
|
PGVectorStoreProvider: () => PGVectorStoreProvider,
|
|
40
|
-
Pool: () =>
|
|
40
|
+
Pool: () => import_pg31.Pool,
|
|
41
41
|
PostgreSQLA2AApiKeyStore: () => PostgreSQLA2AApiKeyStore,
|
|
42
42
|
PostgreSQLAgentWebAppStore: () => PostgreSQLAgentWebAppStore,
|
|
43
43
|
PostgreSQLAssistantStore: () => PostgreSQLAssistantStore,
|
|
@@ -49,6 +49,7 @@ __export(index_exports, {
|
|
|
49
49
|
PostgreSQLEvalStore: () => PostgreSQLEvalStore,
|
|
50
50
|
PostgreSQLMcpServerConfigStore: () => PostgreSQLMcpServerConfigStore,
|
|
51
51
|
PostgreSQLMetricsServerConfigStore: () => PostgreSQLMetricsServerConfigStore,
|
|
52
|
+
PostgreSQLModelProviderStore: () => PostgreSQLModelProviderStore,
|
|
52
53
|
PostgreSQLOpenAuditStore: () => PostgreSQLOpenAuditStore,
|
|
53
54
|
PostgreSQLProjectBotMembershipStore: () => PostgreSQLProjectBotMembershipStore,
|
|
54
55
|
PostgreSQLProjectMembershipStore: () => PostgreSQLProjectMembershipStore,
|
|
@@ -126,10 +127,10 @@ __export(index_exports, {
|
|
|
126
127
|
safeParse: () => safeParse
|
|
127
128
|
});
|
|
128
129
|
module.exports = __toCommonJS(index_exports);
|
|
129
|
-
var
|
|
130
|
+
var import_pg31 = require("pg");
|
|
130
131
|
|
|
131
132
|
// src/createPgStoreConfig.ts
|
|
132
|
-
var
|
|
133
|
+
var import_pg28 = require("pg");
|
|
133
134
|
|
|
134
135
|
// src/migrations/migration.ts
|
|
135
136
|
var MigrationManager = class {
|
|
@@ -10079,6 +10080,116 @@ var createUserPushSubscriptions = {
|
|
|
10079
10080
|
}
|
|
10080
10081
|
};
|
|
10081
10082
|
|
|
10083
|
+
// src/migrations/model_provider_migrations.ts
|
|
10084
|
+
var createModelProvidersTable = {
|
|
10085
|
+
version: 181,
|
|
10086
|
+
name: "create_model_providers_table",
|
|
10087
|
+
up: async (client) => {
|
|
10088
|
+
await client.query(`
|
|
10089
|
+
CREATE TABLE IF NOT EXISTS lattice_model_providers (
|
|
10090
|
+
id VARCHAR(255) NOT NULL,
|
|
10091
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
10092
|
+
name VARCHAR(255) NOT NULL,
|
|
10093
|
+
display_name VARCHAR(255),
|
|
10094
|
+
protocol VARCHAR(64) NOT NULL,
|
|
10095
|
+
api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions',
|
|
10096
|
+
llm_provider VARCHAR(64) NOT NULL,
|
|
10097
|
+
base_url TEXT NOT NULL,
|
|
10098
|
+
api_key_enc TEXT,
|
|
10099
|
+
api_key_hint VARCHAR(16),
|
|
10100
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
10101
|
+
status VARCHAR(32) NOT NULL DEFAULT 'unknown',
|
|
10102
|
+
last_error VARCHAR(255),
|
|
10103
|
+
last_discovered_at TIMESTAMP,
|
|
10104
|
+
created_by VARCHAR(255),
|
|
10105
|
+
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10106
|
+
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10107
|
+
|
|
10108
|
+
PRIMARY KEY (tenant_id, id),
|
|
10109
|
+
CONSTRAINT uk_lattice_model_providers_tenant_name UNIQUE (tenant_id, name)
|
|
10110
|
+
)
|
|
10111
|
+
`);
|
|
10112
|
+
await client.query(`
|
|
10113
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_model_providers_tenant_id
|
|
10114
|
+
ON lattice_model_providers(tenant_id)
|
|
10115
|
+
`);
|
|
10116
|
+
},
|
|
10117
|
+
down: async (client) => {
|
|
10118
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_model_providers_tenant_id");
|
|
10119
|
+
await client.query("DROP TABLE IF EXISTS lattice_model_providers");
|
|
10120
|
+
}
|
|
10121
|
+
};
|
|
10122
|
+
var createModelProviderModelsTable = {
|
|
10123
|
+
version: 182,
|
|
10124
|
+
name: "create_model_provider_models_table",
|
|
10125
|
+
up: async (client) => {
|
|
10126
|
+
await client.query(`
|
|
10127
|
+
CREATE TABLE IF NOT EXISTS lattice_model_provider_models (
|
|
10128
|
+
id VARCHAR(255) NOT NULL,
|
|
10129
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
10130
|
+
provider_id VARCHAR(255) NOT NULL,
|
|
10131
|
+
model_id VARCHAR(512) NOT NULL,
|
|
10132
|
+
display_name VARCHAR(255),
|
|
10133
|
+
owned_by VARCHAR(255),
|
|
10134
|
+
upstream_created TIMESTAMP,
|
|
10135
|
+
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
|
10136
|
+
raw JSONB,
|
|
10137
|
+
discovered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
10138
|
+
|
|
10139
|
+
PRIMARY KEY (tenant_id, provider_id, model_id)
|
|
10140
|
+
)
|
|
10141
|
+
`);
|
|
10142
|
+
await client.query(`
|
|
10143
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_id
|
|
10144
|
+
ON lattice_model_provider_models(tenant_id)
|
|
10145
|
+
`);
|
|
10146
|
+
await client.query(`
|
|
10147
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_model
|
|
10148
|
+
ON lattice_model_provider_models(tenant_id, model_id)
|
|
10149
|
+
`);
|
|
10150
|
+
await client.query(`
|
|
10151
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_provider
|
|
10152
|
+
ON lattice_model_provider_models(tenant_id, provider_id)
|
|
10153
|
+
`);
|
|
10154
|
+
},
|
|
10155
|
+
down: async (client) => {
|
|
10156
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_provider");
|
|
10157
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_model");
|
|
10158
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_id");
|
|
10159
|
+
await client.query("DROP TABLE IF EXISTS lattice_model_provider_models");
|
|
10160
|
+
}
|
|
10161
|
+
};
|
|
10162
|
+
var addModelProviderApiStyle = {
|
|
10163
|
+
version: 183,
|
|
10164
|
+
name: "add_model_provider_api_style",
|
|
10165
|
+
up: async (client) => {
|
|
10166
|
+
await client.query(`
|
|
10167
|
+
ALTER TABLE lattice_model_providers
|
|
10168
|
+
ADD COLUMN IF NOT EXISTS api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions'
|
|
10169
|
+
`);
|
|
10170
|
+
},
|
|
10171
|
+
down: async (client) => {
|
|
10172
|
+
await client.query(`
|
|
10173
|
+
ALTER TABLE lattice_model_providers DROP COLUMN IF EXISTS api_style
|
|
10174
|
+
`);
|
|
10175
|
+
}
|
|
10176
|
+
};
|
|
10177
|
+
var addModelProviderModelStale = {
|
|
10178
|
+
version: 184,
|
|
10179
|
+
name: "add_model_provider_model_stale",
|
|
10180
|
+
up: async (client) => {
|
|
10181
|
+
await client.query(`
|
|
10182
|
+
ALTER TABLE lattice_model_provider_models
|
|
10183
|
+
ADD COLUMN IF NOT EXISTS stale BOOLEAN NOT NULL DEFAULT FALSE
|
|
10184
|
+
`);
|
|
10185
|
+
},
|
|
10186
|
+
down: async (client) => {
|
|
10187
|
+
await client.query(`
|
|
10188
|
+
ALTER TABLE lattice_model_provider_models DROP COLUMN IF EXISTS stale
|
|
10189
|
+
`);
|
|
10190
|
+
}
|
|
10191
|
+
};
|
|
10192
|
+
|
|
10082
10193
|
// src/stores/PostgreSQLCapabilityBundleStore.ts
|
|
10083
10194
|
var import_crypto4 = require("crypto");
|
|
10084
10195
|
var import_pg26 = require("pg");
|
|
@@ -11040,9 +11151,513 @@ var PostgreSQLUserPushSubscriptionStore = class {
|
|
|
11040
11151
|
}
|
|
11041
11152
|
};
|
|
11042
11153
|
|
|
11154
|
+
// src/stores/PostgreSQLModelProviderStore.ts
|
|
11155
|
+
var import_pg27 = require("pg");
|
|
11156
|
+
var import_core7 = require("@axiom-lattice/core");
|
|
11157
|
+
var PostgreSQLModelProviderStore = class {
|
|
11158
|
+
constructor(options) {
|
|
11159
|
+
this.initialized = false;
|
|
11160
|
+
this.ownsPool = true;
|
|
11161
|
+
this.initPromise = null;
|
|
11162
|
+
if (options.pool) {
|
|
11163
|
+
this.pool = options.pool;
|
|
11164
|
+
this.ownsPool = false;
|
|
11165
|
+
this.initialized = true;
|
|
11166
|
+
return;
|
|
11167
|
+
}
|
|
11168
|
+
if (typeof options.poolConfig === "string") {
|
|
11169
|
+
this.pool = new import_pg27.Pool({ connectionString: options.poolConfig });
|
|
11170
|
+
} else if (options.poolConfig) {
|
|
11171
|
+
this.pool = new import_pg27.Pool(options.poolConfig);
|
|
11172
|
+
} else {
|
|
11173
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
11174
|
+
}
|
|
11175
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
11176
|
+
this.migrationManager.register(createModelProvidersTable);
|
|
11177
|
+
this.migrationManager.register(createModelProviderModelsTable);
|
|
11178
|
+
if (options.autoMigrate !== false) {
|
|
11179
|
+
this.initialize().catch((error) => {
|
|
11180
|
+
console.error("Failed to initialize PostgreSQLModelProviderStore:", error);
|
|
11181
|
+
throw error;
|
|
11182
|
+
});
|
|
11183
|
+
}
|
|
11184
|
+
}
|
|
11185
|
+
/**
|
|
11186
|
+
* Initialize the store and run migrations
|
|
11187
|
+
* Uses a promise-based lock to prevent concurrent initialization
|
|
11188
|
+
*/
|
|
11189
|
+
async initialize() {
|
|
11190
|
+
if (this.initialized) {
|
|
11191
|
+
return;
|
|
11192
|
+
}
|
|
11193
|
+
if (this.initPromise) {
|
|
11194
|
+
return this.initPromise;
|
|
11195
|
+
}
|
|
11196
|
+
this.initPromise = (async () => {
|
|
11197
|
+
try {
|
|
11198
|
+
await this.migrationManager.migrate();
|
|
11199
|
+
this.initialized = true;
|
|
11200
|
+
} finally {
|
|
11201
|
+
this.initPromise = null;
|
|
11202
|
+
}
|
|
11203
|
+
})();
|
|
11204
|
+
return this.initPromise;
|
|
11205
|
+
}
|
|
11206
|
+
/**
|
|
11207
|
+
* Get all provider configurations for a tenant
|
|
11208
|
+
*/
|
|
11209
|
+
async listProviders(tenantId) {
|
|
11210
|
+
await this.ensureInitialized();
|
|
11211
|
+
const result = await this.pool.query(
|
|
11212
|
+
`
|
|
11213
|
+
SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
|
|
11214
|
+
api_key_enc, api_key_hint, enabled, status, last_error,
|
|
11215
|
+
last_discovered_at, created_by, created_at, updated_at
|
|
11216
|
+
FROM lattice_model_providers
|
|
11217
|
+
WHERE tenant_id = $1
|
|
11218
|
+
ORDER BY created_at DESC
|
|
11219
|
+
`,
|
|
11220
|
+
[tenantId]
|
|
11221
|
+
);
|
|
11222
|
+
return result.rows.map((row) => this.mapRowToEntry(row));
|
|
11223
|
+
}
|
|
11224
|
+
/**
|
|
11225
|
+
* Get a provider configuration by ID
|
|
11226
|
+
*/
|
|
11227
|
+
async getProviderById(tenantId, id) {
|
|
11228
|
+
await this.ensureInitialized();
|
|
11229
|
+
const result = await this.pool.query(
|
|
11230
|
+
`
|
|
11231
|
+
SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
|
|
11232
|
+
api_key_enc, api_key_hint, enabled, status, last_error,
|
|
11233
|
+
last_discovered_at, created_by, created_at, updated_at
|
|
11234
|
+
FROM lattice_model_providers
|
|
11235
|
+
WHERE tenant_id = $1 AND id = $2
|
|
11236
|
+
`,
|
|
11237
|
+
[tenantId, id]
|
|
11238
|
+
);
|
|
11239
|
+
if (result.rows.length === 0) {
|
|
11240
|
+
return null;
|
|
11241
|
+
}
|
|
11242
|
+
return this.mapRowToEntry(result.rows[0]);
|
|
11243
|
+
}
|
|
11244
|
+
/**
|
|
11245
|
+
* Get a provider configuration by business name
|
|
11246
|
+
*/
|
|
11247
|
+
async getProviderByName(tenantId, name) {
|
|
11248
|
+
await this.ensureInitialized();
|
|
11249
|
+
const result = await this.pool.query(
|
|
11250
|
+
`
|
|
11251
|
+
SELECT id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
|
|
11252
|
+
api_key_enc, api_key_hint, enabled, status, last_error,
|
|
11253
|
+
last_discovered_at, created_by, created_at, updated_at
|
|
11254
|
+
FROM lattice_model_providers
|
|
11255
|
+
WHERE tenant_id = $1 AND name = $2
|
|
11256
|
+
`,
|
|
11257
|
+
[tenantId, name]
|
|
11258
|
+
);
|
|
11259
|
+
if (result.rows.length === 0) {
|
|
11260
|
+
return null;
|
|
11261
|
+
}
|
|
11262
|
+
return this.mapRowToEntry(result.rows[0]);
|
|
11263
|
+
}
|
|
11264
|
+
/**
|
|
11265
|
+
* Create a new provider configuration
|
|
11266
|
+
*/
|
|
11267
|
+
async createProvider(tenantId, id, data) {
|
|
11268
|
+
await this.ensureInitialized();
|
|
11269
|
+
const now = /* @__PURE__ */ new Date();
|
|
11270
|
+
const nowString = now.toISOString();
|
|
11271
|
+
const apiKeyEnc = data.apiKey ? (0, import_core7.encrypt)(data.apiKey) : null;
|
|
11272
|
+
const apiKeyHint = data.apiKey ? data.apiKey.slice(-4) : null;
|
|
11273
|
+
await this.pool.query(
|
|
11274
|
+
`
|
|
11275
|
+
INSERT INTO lattice_model_providers (
|
|
11276
|
+
id, tenant_id, name, display_name, protocol, api_style, llm_provider, base_url,
|
|
11277
|
+
api_key_enc, api_key_hint, enabled, status, created_at, updated_at
|
|
11278
|
+
)
|
|
11279
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, 'unknown', $12::timestamp, $13::timestamp)
|
|
11280
|
+
ON CONFLICT (tenant_id, id) DO UPDATE SET
|
|
11281
|
+
name = EXCLUDED.name,
|
|
11282
|
+
display_name = EXCLUDED.display_name,
|
|
11283
|
+
protocol = EXCLUDED.protocol,
|
|
11284
|
+
api_style = EXCLUDED.api_style,
|
|
11285
|
+
llm_provider = EXCLUDED.llm_provider,
|
|
11286
|
+
base_url = EXCLUDED.base_url,
|
|
11287
|
+
api_key_enc = EXCLUDED.api_key_enc,
|
|
11288
|
+
api_key_hint = EXCLUDED.api_key_hint,
|
|
11289
|
+
enabled = EXCLUDED.enabled,
|
|
11290
|
+
updated_at = EXCLUDED.updated_at
|
|
11291
|
+
`,
|
|
11292
|
+
[
|
|
11293
|
+
id,
|
|
11294
|
+
tenantId,
|
|
11295
|
+
data.name,
|
|
11296
|
+
data.displayName || null,
|
|
11297
|
+
data.protocol,
|
|
11298
|
+
data.apiStyle || "chat-completions",
|
|
11299
|
+
// Phase 1 pins the LLM provider: the UI does not offer a choice, and
|
|
11300
|
+
// some initChatModel branches hardcode their own baseURL and would
|
|
11301
|
+
// silently ignore the tenant's (see ADR-138 decision 7b).
|
|
11302
|
+
"openai",
|
|
11303
|
+
data.baseURL,
|
|
11304
|
+
apiKeyEnc,
|
|
11305
|
+
apiKeyHint,
|
|
11306
|
+
data.enabled !== false,
|
|
11307
|
+
nowString,
|
|
11308
|
+
nowString
|
|
11309
|
+
]
|
|
11310
|
+
);
|
|
11311
|
+
const created = await this.getProviderById(tenantId, id);
|
|
11312
|
+
if (!created) {
|
|
11313
|
+
throw new Error("Failed to create model provider");
|
|
11314
|
+
}
|
|
11315
|
+
return created;
|
|
11316
|
+
}
|
|
11317
|
+
/**
|
|
11318
|
+
* Update an existing provider configuration.
|
|
11319
|
+
* An omitted or empty `apiKey` leaves the stored key untouched.
|
|
11320
|
+
*/
|
|
11321
|
+
async updateProvider(tenantId, id, updates) {
|
|
11322
|
+
await this.ensureInitialized();
|
|
11323
|
+
const existing = await this.getProviderById(tenantId, id);
|
|
11324
|
+
if (!existing) {
|
|
11325
|
+
return null;
|
|
11326
|
+
}
|
|
11327
|
+
const updateData = {};
|
|
11328
|
+
if (updates.name !== void 0) {
|
|
11329
|
+
updateData.name = updates.name;
|
|
11330
|
+
}
|
|
11331
|
+
if (updates.displayName !== void 0) {
|
|
11332
|
+
updateData.display_name = updates.displayName || null;
|
|
11333
|
+
}
|
|
11334
|
+
if (updates.protocol !== void 0) {
|
|
11335
|
+
updateData.protocol = updates.protocol;
|
|
11336
|
+
}
|
|
11337
|
+
if (updates.apiStyle !== void 0) {
|
|
11338
|
+
updateData.api_style = updates.apiStyle;
|
|
11339
|
+
}
|
|
11340
|
+
if (updates.baseURL !== void 0) {
|
|
11341
|
+
updateData.base_url = updates.baseURL;
|
|
11342
|
+
}
|
|
11343
|
+
if (updates.clearApiKey === true) {
|
|
11344
|
+
updateData.api_key_enc = null;
|
|
11345
|
+
updateData.api_key_hint = null;
|
|
11346
|
+
} else if (updates.apiKey) {
|
|
11347
|
+
updateData.api_key_enc = (0, import_core7.encrypt)(updates.apiKey);
|
|
11348
|
+
updateData.api_key_hint = updates.apiKey.slice(-4);
|
|
11349
|
+
}
|
|
11350
|
+
if (updates.enabled !== void 0) {
|
|
11351
|
+
updateData.enabled = updates.enabled;
|
|
11352
|
+
}
|
|
11353
|
+
if (Object.keys(updateData).length === 0) {
|
|
11354
|
+
return existing;
|
|
11355
|
+
}
|
|
11356
|
+
updateData.updated_at = (/* @__PURE__ */ new Date()).toISOString();
|
|
11357
|
+
const fields = Object.keys(updateData);
|
|
11358
|
+
const values = Object.values(updateData);
|
|
11359
|
+
values.push(tenantId);
|
|
11360
|
+
values.push(id);
|
|
11361
|
+
const setClauses = fields.map(
|
|
11362
|
+
(field, index) => field === "updated_at" ? `${field} = $${index + 1}::timestamp` : `${field} = $${index + 1}`
|
|
11363
|
+
);
|
|
11364
|
+
const whereTenantIndex = fields.length + 1;
|
|
11365
|
+
const whereIdIndex = fields.length + 2;
|
|
11366
|
+
const sql = `
|
|
11367
|
+
UPDATE lattice_model_providers
|
|
11368
|
+
SET ${setClauses.join(", ")}
|
|
11369
|
+
WHERE tenant_id = $${whereTenantIndex} AND id = $${whereIdIndex}
|
|
11370
|
+
`;
|
|
11371
|
+
await this.pool.query(sql, values);
|
|
11372
|
+
return await this.getProviderById(tenantId, id);
|
|
11373
|
+
}
|
|
11374
|
+
/**
|
|
11375
|
+
* Delete a provider configuration and its model snapshot
|
|
11376
|
+
*/
|
|
11377
|
+
async deleteProvider(tenantId, id) {
|
|
11378
|
+
await this.ensureInitialized();
|
|
11379
|
+
await this.deleteModelsByProvider(tenantId, id);
|
|
11380
|
+
const result = await this.pool.query(
|
|
11381
|
+
`
|
|
11382
|
+
DELETE FROM lattice_model_providers
|
|
11383
|
+
WHERE tenant_id = $1 AND id = $2
|
|
11384
|
+
`,
|
|
11385
|
+
[tenantId, id]
|
|
11386
|
+
);
|
|
11387
|
+
return result.rowCount !== null && result.rowCount > 0;
|
|
11388
|
+
}
|
|
11389
|
+
/**
|
|
11390
|
+
* Record the outcome of a discovery attempt
|
|
11391
|
+
*/
|
|
11392
|
+
async markProviderStatus(tenantId, id, status, lastError, discoveredAt) {
|
|
11393
|
+
await this.ensureInitialized();
|
|
11394
|
+
await this.pool.query(
|
|
11395
|
+
`
|
|
11396
|
+
UPDATE lattice_model_providers
|
|
11397
|
+
SET status = $3,
|
|
11398
|
+
last_error = $4,
|
|
11399
|
+
last_discovered_at = CASE
|
|
11400
|
+
WHEN $5::timestamp IS NULL THEN last_discovered_at
|
|
11401
|
+
ELSE $5::timestamp
|
|
11402
|
+
END,
|
|
11403
|
+
updated_at = $6::timestamp
|
|
11404
|
+
WHERE tenant_id = $1 AND id = $2
|
|
11405
|
+
`,
|
|
11406
|
+
[
|
|
11407
|
+
tenantId,
|
|
11408
|
+
id,
|
|
11409
|
+
status,
|
|
11410
|
+
status === "ok" ? null : lastError || null,
|
|
11411
|
+
discoveredAt ? discoveredAt.toISOString() : null,
|
|
11412
|
+
(/* @__PURE__ */ new Date()).toISOString()
|
|
11413
|
+
]
|
|
11414
|
+
);
|
|
11415
|
+
}
|
|
11416
|
+
/**
|
|
11417
|
+
* List snapshot models for a tenant
|
|
11418
|
+
*/
|
|
11419
|
+
async listModels(tenantId, providerId) {
|
|
11420
|
+
await this.ensureInitialized();
|
|
11421
|
+
const result = providerId ? await this.pool.query(
|
|
11422
|
+
`
|
|
11423
|
+
SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
|
|
11424
|
+
upstream_created, enabled, stale, discovered_at
|
|
11425
|
+
FROM lattice_model_provider_models
|
|
11426
|
+
WHERE tenant_id = $1 AND provider_id = $2
|
|
11427
|
+
ORDER BY model_id ASC
|
|
11428
|
+
`,
|
|
11429
|
+
[tenantId, providerId]
|
|
11430
|
+
) : await this.pool.query(
|
|
11431
|
+
`
|
|
11432
|
+
SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
|
|
11433
|
+
upstream_created, enabled, discovered_at
|
|
11434
|
+
FROM lattice_model_provider_models
|
|
11435
|
+
WHERE tenant_id = $1
|
|
11436
|
+
ORDER BY model_id ASC
|
|
11437
|
+
`,
|
|
11438
|
+
[tenantId]
|
|
11439
|
+
);
|
|
11440
|
+
return result.rows.map((row) => this.mapRowToModel(row));
|
|
11441
|
+
}
|
|
11442
|
+
/**
|
|
11443
|
+
* Get a single snapshot model.
|
|
11444
|
+
* Keyed by provider as well as model id, since two providers in the same
|
|
11445
|
+
* tenant may expose the same upstream model id.
|
|
11446
|
+
*/
|
|
11447
|
+
async getModel(tenantId, providerId, modelId) {
|
|
11448
|
+
await this.ensureInitialized();
|
|
11449
|
+
const result = await this.pool.query(
|
|
11450
|
+
`
|
|
11451
|
+
SELECT id, tenant_id, provider_id, model_id, display_name, owned_by,
|
|
11452
|
+
upstream_created, enabled, discovered_at
|
|
11453
|
+
FROM lattice_model_provider_models
|
|
11454
|
+
WHERE tenant_id = $1 AND provider_id = $2 AND model_id = $3
|
|
11455
|
+
`,
|
|
11456
|
+
[tenantId, providerId, modelId]
|
|
11457
|
+
);
|
|
11458
|
+
if (result.rows.length === 0) {
|
|
11459
|
+
return null;
|
|
11460
|
+
}
|
|
11461
|
+
return this.mapRowToModel(result.rows[0]);
|
|
11462
|
+
}
|
|
11463
|
+
/**
|
|
11464
|
+
* Replace a provider's model snapshot with the result of one discovery.
|
|
11465
|
+
*
|
|
11466
|
+
* Runs in a single transaction. Model ids are scoped per provider, so the
|
|
11467
|
+
* same upstream id may appear under several providers in one tenant — each
|
|
11468
|
+
* is an independent model.
|
|
11469
|
+
*
|
|
11470
|
+
* Models the upstream no longer offers are deleted; models that survive are
|
|
11471
|
+
* upserted *without* touching `enabled`, so the tenant's enable/disable
|
|
11472
|
+
* choice survives a re-discovery. New rows take the column default (enabled).
|
|
11473
|
+
*/
|
|
11474
|
+
async replaceProviderModels(tenantId, providerId, models) {
|
|
11475
|
+
await this.ensureInitialized();
|
|
11476
|
+
const client = await this.pool.connect();
|
|
11477
|
+
try {
|
|
11478
|
+
await client.query("BEGIN");
|
|
11479
|
+
const current = await client.query(
|
|
11480
|
+
`
|
|
11481
|
+
SELECT model_id, enabled FROM lattice_model_provider_models
|
|
11482
|
+
WHERE tenant_id = $1 AND provider_id = $2
|
|
11483
|
+
`,
|
|
11484
|
+
[tenantId, providerId]
|
|
11485
|
+
);
|
|
11486
|
+
const currentEnabled = new Map(
|
|
11487
|
+
current.rows.map((row) => [row.model_id, row.enabled])
|
|
11488
|
+
);
|
|
11489
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
11490
|
+
for (const model of models) {
|
|
11491
|
+
deduped.set(model.id, model);
|
|
11492
|
+
}
|
|
11493
|
+
const rows = [...deduped.values()];
|
|
11494
|
+
const effectiveEnabled = rows.map(
|
|
11495
|
+
(model) => model.enabled ?? currentEnabled.get(model.id) ?? true
|
|
11496
|
+
);
|
|
11497
|
+
await client.query(
|
|
11498
|
+
`
|
|
11499
|
+
DELETE FROM lattice_model_provider_models
|
|
11500
|
+
WHERE tenant_id = $1
|
|
11501
|
+
AND provider_id = $2
|
|
11502
|
+
AND model_id <> ALL($3::text[])
|
|
11503
|
+
`,
|
|
11504
|
+
[tenantId, providerId, rows.map((model) => model.id)]
|
|
11505
|
+
);
|
|
11506
|
+
if (models.length > 0) {
|
|
11507
|
+
await client.query(
|
|
11508
|
+
`
|
|
11509
|
+
INSERT INTO lattice_model_provider_models (
|
|
11510
|
+
id, tenant_id, provider_id, model_id, display_name, owned_by,
|
|
11511
|
+
upstream_created, raw, enabled, stale, discovered_at
|
|
11512
|
+
)
|
|
11513
|
+
SELECT * FROM unnest(
|
|
11514
|
+
$1::text[], $2::text[], $3::text[], $4::text[], $5::text[],
|
|
11515
|
+
$6::text[], $7::timestamp[], $8::jsonb[], $9::boolean[], $10::boolean[], $11::timestamp[]
|
|
11516
|
+
) AS t(
|
|
11517
|
+
id, tenant_id, provider_id, model_id, display_name, owned_by,
|
|
11518
|
+
upstream_created, raw, enabled, stale, discovered_at
|
|
11519
|
+
)
|
|
11520
|
+
ON CONFLICT (tenant_id, provider_id, model_id) DO UPDATE SET
|
|
11521
|
+
display_name = EXCLUDED.display_name,
|
|
11522
|
+
owned_by = EXCLUDED.owned_by,
|
|
11523
|
+
upstream_created = EXCLUDED.upstream_created,
|
|
11524
|
+
raw = EXCLUDED.raw,
|
|
11525
|
+
enabled = EXCLUDED.enabled,
|
|
11526
|
+
stale = EXCLUDED.stale,
|
|
11527
|
+
discovered_at = EXCLUDED.discovered_at
|
|
11528
|
+
`,
|
|
11529
|
+
[
|
|
11530
|
+
rows.map((model) => `${providerId}:${model.id}`),
|
|
11531
|
+
rows.map(() => tenantId),
|
|
11532
|
+
rows.map(() => providerId),
|
|
11533
|
+
rows.map((model) => model.id),
|
|
11534
|
+
rows.map((model) => model.displayName ?? null),
|
|
11535
|
+
rows.map((model) => model.ownedBy ?? null),
|
|
11536
|
+
rows.map(
|
|
11537
|
+
(model) => model.createdAt ? new Date(model.createdAt).toISOString() : null
|
|
11538
|
+
),
|
|
11539
|
+
rows.map(
|
|
11540
|
+
(model) => model.raw === void 0 ? null : JSON.stringify(model.raw)
|
|
11541
|
+
),
|
|
11542
|
+
effectiveEnabled,
|
|
11543
|
+
rows.map((model) => model.stale ?? false),
|
|
11544
|
+
rows.map(() => (/* @__PURE__ */ new Date()).toISOString())
|
|
11545
|
+
]
|
|
11546
|
+
);
|
|
11547
|
+
}
|
|
11548
|
+
await client.query("COMMIT");
|
|
11549
|
+
} catch (error) {
|
|
11550
|
+
await client.query("ROLLBACK");
|
|
11551
|
+
throw error;
|
|
11552
|
+
} finally {
|
|
11553
|
+
client.release();
|
|
11554
|
+
}
|
|
11555
|
+
return models.map((model) => model.id);
|
|
11556
|
+
}
|
|
11557
|
+
/**
|
|
11558
|
+
* Enable or disable a single snapshot model
|
|
11559
|
+
*/
|
|
11560
|
+
async setModelEnabled(tenantId, providerId, modelId, enabled) {
|
|
11561
|
+
await this.ensureInitialized();
|
|
11562
|
+
const result = await this.pool.query(
|
|
11563
|
+
`
|
|
11564
|
+
UPDATE lattice_model_provider_models
|
|
11565
|
+
SET enabled = $4
|
|
11566
|
+
WHERE tenant_id = $1 AND provider_id = $2 AND model_id = $3
|
|
11567
|
+
`,
|
|
11568
|
+
[tenantId, providerId, modelId, enabled]
|
|
11569
|
+
);
|
|
11570
|
+
if (result.rowCount === null || result.rowCount === 0) {
|
|
11571
|
+
return null;
|
|
11572
|
+
}
|
|
11573
|
+
return await this.getModel(tenantId, providerId, modelId);
|
|
11574
|
+
}
|
|
11575
|
+
/**
|
|
11576
|
+
* Delete every snapshot model belonging to a provider
|
|
11577
|
+
*/
|
|
11578
|
+
async deleteModelsByProvider(tenantId, providerId) {
|
|
11579
|
+
await this.ensureInitialized();
|
|
11580
|
+
const result = await this.pool.query(
|
|
11581
|
+
`
|
|
11582
|
+
DELETE FROM lattice_model_provider_models
|
|
11583
|
+
WHERE tenant_id = $1 AND provider_id = $2
|
|
11584
|
+
`,
|
|
11585
|
+
[tenantId, providerId]
|
|
11586
|
+
);
|
|
11587
|
+
return result.rowCount ?? 0;
|
|
11588
|
+
}
|
|
11589
|
+
/**
|
|
11590
|
+
* Dispose resources and close the connection pool
|
|
11591
|
+
*/
|
|
11592
|
+
async dispose() {
|
|
11593
|
+
if (this.ownsPool && this.pool) {
|
|
11594
|
+
await this.pool.end();
|
|
11595
|
+
}
|
|
11596
|
+
}
|
|
11597
|
+
/**
|
|
11598
|
+
* Ensure store is initialized
|
|
11599
|
+
*/
|
|
11600
|
+
async ensureInitialized() {
|
|
11601
|
+
if (!this.initialized) {
|
|
11602
|
+
await this.initialize();
|
|
11603
|
+
}
|
|
11604
|
+
}
|
|
11605
|
+
/**
|
|
11606
|
+
* Map provider row to ModelProviderEntry
|
|
11607
|
+
* Automatically decrypts the API key if present
|
|
11608
|
+
*/
|
|
11609
|
+
mapRowToEntry(row) {
|
|
11610
|
+
let apiKey;
|
|
11611
|
+
if (row.api_key_enc) {
|
|
11612
|
+
try {
|
|
11613
|
+
apiKey = (0, import_core7.decrypt)(row.api_key_enc);
|
|
11614
|
+
} catch (error) {
|
|
11615
|
+
console.error("Failed to decrypt model provider API key:", error);
|
|
11616
|
+
throw new Error("Failed to decrypt model provider API key");
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
return {
|
|
11620
|
+
id: row.id,
|
|
11621
|
+
tenantId: row.tenant_id,
|
|
11622
|
+
name: row.name,
|
|
11623
|
+
displayName: row.display_name || void 0,
|
|
11624
|
+
protocol: row.protocol,
|
|
11625
|
+
apiStyle: row.api_style,
|
|
11626
|
+
llmProvider: row.llm_provider,
|
|
11627
|
+
baseURL: row.base_url,
|
|
11628
|
+
apiKey,
|
|
11629
|
+
apiKeyHint: row.api_key_hint || void 0,
|
|
11630
|
+
enabled: row.enabled,
|
|
11631
|
+
status: row.status,
|
|
11632
|
+
lastError: row.last_error || void 0,
|
|
11633
|
+
lastDiscoveredAt: row.last_discovered_at || void 0,
|
|
11634
|
+
createdBy: row.created_by || void 0,
|
|
11635
|
+
createdAt: row.created_at,
|
|
11636
|
+
updatedAt: row.updated_at
|
|
11637
|
+
};
|
|
11638
|
+
}
|
|
11639
|
+
/**
|
|
11640
|
+
* Map model row to ModelProviderModelEntry
|
|
11641
|
+
*/
|
|
11642
|
+
mapRowToModel(row) {
|
|
11643
|
+
return {
|
|
11644
|
+
id: row.id,
|
|
11645
|
+
tenantId: row.tenant_id,
|
|
11646
|
+
providerId: row.provider_id,
|
|
11647
|
+
modelId: row.model_id,
|
|
11648
|
+
displayName: row.display_name || void 0,
|
|
11649
|
+
ownedBy: row.owned_by || void 0,
|
|
11650
|
+
upstreamCreatedAt: row.upstream_created || void 0,
|
|
11651
|
+
enabled: row.enabled,
|
|
11652
|
+
stale: row.stale,
|
|
11653
|
+
discoveredAt: row.discovered_at
|
|
11654
|
+
};
|
|
11655
|
+
}
|
|
11656
|
+
};
|
|
11657
|
+
|
|
11043
11658
|
// src/createPgStoreConfig.ts
|
|
11044
11659
|
async function createPgStoreConfig(connectionString) {
|
|
11045
|
-
const pool = new
|
|
11660
|
+
const pool = new import_pg28.Pool({ connectionString });
|
|
11046
11661
|
const mm = new MigrationManager(pool);
|
|
11047
11662
|
mm.register(createThreadsTable);
|
|
11048
11663
|
mm.register(createScheduledTasksTable);
|
|
@@ -11110,6 +11725,10 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11110
11725
|
mm.register(createProjectRoomReadStates);
|
|
11111
11726
|
mm.register(createUserPushSubscriptions);
|
|
11112
11727
|
mm.register(addProjectRoomMessageRetentionIndex);
|
|
11728
|
+
mm.register(createModelProvidersTable);
|
|
11729
|
+
mm.register(createModelProviderModelsTable);
|
|
11730
|
+
mm.register(addModelProviderApiStyle);
|
|
11731
|
+
mm.register(addModelProviderModelStale);
|
|
11113
11732
|
await mm.migrate();
|
|
11114
11733
|
const checkpoint = import_langgraph_checkpoint_postgres.PostgresSaver.fromConnString(connectionString);
|
|
11115
11734
|
checkpoint.setup().catch((err) => {
|
|
@@ -11131,6 +11750,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11131
11750
|
connection: new PostgreSQLConnectionStore(pool),
|
|
11132
11751
|
metrics: new PostgreSQLMetricsServerConfigStore(opts),
|
|
11133
11752
|
mcp: new PostgreSQLMcpServerConfigStore(opts),
|
|
11753
|
+
modelProvider: new PostgreSQLModelProviderStore(opts),
|
|
11134
11754
|
assistant: new PostgreSQLAssistantStore(opts),
|
|
11135
11755
|
workflowTracking: new PostgreSQLWorkflowTrackingStore(opts),
|
|
11136
11756
|
threadMessageQueue: new ThreadMessageQueueStore(opts),
|
|
@@ -11156,7 +11776,7 @@ async function createPgStoreConfig(connectionString) {
|
|
|
11156
11776
|
}
|
|
11157
11777
|
|
|
11158
11778
|
// src/stores/PostgreSQLSkillStore.ts
|
|
11159
|
-
var
|
|
11779
|
+
var import_pg29 = require("pg");
|
|
11160
11780
|
var PostgreSQLSkillStore = class {
|
|
11161
11781
|
constructor(options) {
|
|
11162
11782
|
this.initialized = false;
|
|
@@ -11169,9 +11789,9 @@ var PostgreSQLSkillStore = class {
|
|
|
11169
11789
|
return;
|
|
11170
11790
|
}
|
|
11171
11791
|
if (typeof options.poolConfig === "string") {
|
|
11172
|
-
this.pool = new
|
|
11792
|
+
this.pool = new import_pg29.Pool({ connectionString: options.poolConfig });
|
|
11173
11793
|
} else if (options.poolConfig) {
|
|
11174
|
-
this.pool = new
|
|
11794
|
+
this.pool = new import_pg29.Pool(options.poolConfig);
|
|
11175
11795
|
} else {
|
|
11176
11796
|
throw new Error("Either pool or poolConfig must be provided");
|
|
11177
11797
|
}
|
|
@@ -11472,7 +12092,7 @@ var PostgreSQLSkillStore = class {
|
|
|
11472
12092
|
};
|
|
11473
12093
|
|
|
11474
12094
|
// src/stores/ChannelIdentityMappingStore.ts
|
|
11475
|
-
var
|
|
12095
|
+
var import_pg30 = require("pg");
|
|
11476
12096
|
var ChannelIdentityMappingStore = class {
|
|
11477
12097
|
constructor(options) {
|
|
11478
12098
|
this.initialized = false;
|
|
@@ -11484,7 +12104,7 @@ var ChannelIdentityMappingStore = class {
|
|
|
11484
12104
|
this.initialized = true;
|
|
11485
12105
|
return;
|
|
11486
12106
|
}
|
|
11487
|
-
this.pool = typeof options.poolConfig === "string" ? new
|
|
12107
|
+
this.pool = typeof options.poolConfig === "string" ? new import_pg30.Pool({ connectionString: options.poolConfig }) : options.poolConfig ? new import_pg30.Pool(options.poolConfig) : (() => {
|
|
11488
12108
|
throw new Error("Either pool or poolConfig must be provided");
|
|
11489
12109
|
})();
|
|
11490
12110
|
this.migrationManager = new MigrationManager(this.pool);
|
|
@@ -11723,6 +12343,7 @@ function mapRowToChannelIdentityMapping(row) {
|
|
|
11723
12343
|
PostgreSQLEvalStore,
|
|
11724
12344
|
PostgreSQLMcpServerConfigStore,
|
|
11725
12345
|
PostgreSQLMetricsServerConfigStore,
|
|
12346
|
+
PostgreSQLModelProviderStore,
|
|
11726
12347
|
PostgreSQLOpenAuditStore,
|
|
11727
12348
|
PostgreSQLProjectBotMembershipStore,
|
|
11728
12349
|
PostgreSQLProjectMembershipStore,
|