@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axiom-lattice/pg-stores",
3
- "version": "3.1.21",
3
+ "version": "3.2.0",
4
4
  "description": "PG stores implementation for Axiom Lattice framework",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -25,8 +25,8 @@
25
25
  "@langchain/core": "1.1.30",
26
26
  "pg": "^8.16.3",
27
27
  "uuid": "^9.0.1",
28
- "@axiom-lattice/core": "6.0.0",
29
- "@axiom-lattice/protocols": "4.5.0"
28
+ "@axiom-lattice/core": "6.1.0",
29
+ "@axiom-lattice/protocols": "4.6.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/jest": "^29.5.14",
@@ -25,8 +25,13 @@ for (const file of files) {
25
25
  // Extract name and version from migration definitions
26
26
  // Matches patterns like:
27
27
  // name: "create_threads_table",
28
+ // name: 'create_model_providers_table',
28
29
  // version: 1,
29
- const nameMatches = content.matchAll(/^\s*name:\s*"([^"]+)"/gm);
30
+ //
31
+ // Both quote styles are accepted. Migrations written with single quotes were
32
+ // invisible to this check, so their names were never compared and a duplicate
33
+ // could only surface at runtime, where MigrationManager.register throws.
34
+ const nameMatches = content.matchAll(/^\s*name:\s*["']([^"']+)["']/gm);
30
35
  const versionMatches = content.matchAll(/^\s*version:\s*(\d+)/gm);
31
36
 
32
37
  const names = [...nameMatches].map(m => m[1]);
@@ -90,6 +90,12 @@ import { createCapabilityBundlesTable } from "./migrations/capability_bundle_mig
90
90
  import { createProjectRoomReadStates, createProjectRoomTables } from "./migrations/project_room_migration";
91
91
  import { addProjectRoomMessageRetentionIndex } from "./migrations/project_room_message_retention_migration";
92
92
  import { createUserPushSubscriptions } from "./migrations/user_push_subscription_migration";
93
+ import {
94
+ createModelProvidersTable,
95
+ createModelProviderModelsTable,
96
+ addModelProviderApiStyle,
97
+ addModelProviderModelStale,
98
+ } from "./migrations/model_provider_migrations";
93
99
  import { PostgreSQLCapabilityBundleStore } from "./stores/PostgreSQLCapabilityBundleStore";
94
100
  import { PostgreSQLProjectRoomStore } from "./stores/PostgreSQLProjectRoomStore";
95
101
  import { PostgreSQLProjectMembershipStore } from "./stores/PostgreSQLProjectMembershipStore";
@@ -97,6 +103,7 @@ import { PostgreSQLProjectBotMembershipStore } from "./stores/PostgreSQLProjectB
97
103
  import { PostgreSQLProjectRoomMessageStore } from "./stores/PostgreSQLProjectRoomMessageStore";
98
104
  import { PostgreSQLProjectRoomReadStateStore } from "./stores/PostgreSQLProjectRoomReadStateStore";
99
105
  import { PostgreSQLUserPushSubscriptionStore } from "./stores/PostgreSQLUserPushSubscriptionStore";
106
+ import { PostgreSQLModelProviderStore } from "./stores/PostgreSQLModelProviderStore";
100
107
 
101
108
  export async function createPgStoreConfig(connectionString: string) {
102
109
  const pool = new Pool({ connectionString });
@@ -171,6 +178,10 @@ export async function createPgStoreConfig(connectionString: string) {
171
178
  mm.register(createProjectRoomReadStates); // v177
172
179
  mm.register(createUserPushSubscriptions); // v178
173
180
  mm.register(addProjectRoomMessageRetentionIndex); // v179
181
+ mm.register(createModelProvidersTable); // v181
182
+ mm.register(createModelProviderModelsTable); // v182
183
+ mm.register(addModelProviderApiStyle); // v183
184
+ mm.register(addModelProviderModelStale); // v184
174
185
 
175
186
  await mm.migrate();
176
187
 
@@ -197,6 +208,7 @@ export async function createPgStoreConfig(connectionString: string) {
197
208
  connection: new PostgreSQLConnectionStore(pool),
198
209
  metrics: new PostgreSQLMetricsServerConfigStore(opts),
199
210
  mcp: new PostgreSQLMcpServerConfigStore(opts),
211
+ modelProvider: new PostgreSQLModelProviderStore(opts),
200
212
  assistant: new PostgreSQLAssistantStore(opts),
201
213
  workflowTracking: new PostgreSQLWorkflowTrackingStore(opts),
202
214
  threadMessageQueue: new ThreadMessageQueueStore(opts),
package/src/index.ts CHANGED
@@ -18,6 +18,7 @@ export * from "./stores/PostgreSQLDatabaseConfigStore";
18
18
  export * from "./stores/PostgreSQLConnectionStore";
19
19
  export * from "./stores/PostgreSQLMetricsServerConfigStore";
20
20
  export * from "./stores/PostgreSQLMcpServerConfigStore";
21
+ export * from "./stores/PostgreSQLModelProviderStore";
21
22
  export * from "./stores/PostgreSQLWorkspaceStore";
22
23
  export * from "./stores/PostgreSQLProjectStore";
23
24
  export * from "./stores/PostgreSQLUserStore";
@@ -91,6 +92,7 @@ export { PostgreSQLDatabaseConfigStore } from "./stores/PostgreSQLDatabaseConfig
91
92
  export { PostgreSQLConnectionStore } from "./stores/PostgreSQLConnectionStore";
92
93
  export { PostgreSQLMetricsServerConfigStore } from "./stores/PostgreSQLMetricsServerConfigStore";
93
94
  export { PostgreSQLMcpServerConfigStore } from "./stores/PostgreSQLMcpServerConfigStore";
95
+ export { PostgreSQLModelProviderStore } from "./stores/PostgreSQLModelProviderStore";
94
96
  export { PostgreSQLWorkspaceStore } from "./stores/PostgreSQLWorkspaceStore";
95
97
  export { PostgreSQLProjectStore } from "./stores/PostgreSQLProjectStore";
96
98
  export { PostgreSQLUserStore } from "./stores/PostgreSQLUserStore";
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Model provider table migrations
3
+ *
4
+ * Tenants configure an upstream provider (baseURL + apiKey) and the gateway
5
+ * records the models discovered from it as a snapshot.
6
+ */
7
+
8
+ import { PoolClient } from 'pg';
9
+ import { Migration } from './migration';
10
+
11
+ /**
12
+ * Create model provider configuration table
13
+ */
14
+ export const createModelProvidersTable: Migration = {
15
+ version: 181,
16
+ name: 'create_model_providers_table',
17
+ up: async (client: PoolClient) => {
18
+ await client.query(`
19
+ CREATE TABLE IF NOT EXISTS lattice_model_providers (
20
+ id VARCHAR(255) NOT NULL,
21
+ tenant_id VARCHAR(255) NOT NULL,
22
+ name VARCHAR(255) NOT NULL,
23
+ display_name VARCHAR(255),
24
+ protocol VARCHAR(64) NOT NULL,
25
+ api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions',
26
+ llm_provider VARCHAR(64) NOT NULL,
27
+ base_url TEXT NOT NULL,
28
+ api_key_enc TEXT,
29
+ api_key_hint VARCHAR(16),
30
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
31
+ status VARCHAR(32) NOT NULL DEFAULT 'unknown',
32
+ last_error VARCHAR(255),
33
+ last_discovered_at TIMESTAMP,
34
+ created_by VARCHAR(255),
35
+ created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
36
+ updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
37
+
38
+ PRIMARY KEY (tenant_id, id),
39
+ CONSTRAINT uk_lattice_model_providers_tenant_name UNIQUE (tenant_id, name)
40
+ )
41
+ `);
42
+
43
+ await client.query(`
44
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_providers_tenant_id
45
+ ON lattice_model_providers(tenant_id)
46
+ `);
47
+ },
48
+ down: async (client: PoolClient) => {
49
+ await client.query('DROP INDEX IF EXISTS idx_lattice_model_providers_tenant_id');
50
+ await client.query('DROP TABLE IF EXISTS lattice_model_providers');
51
+ },
52
+ };
53
+
54
+ /**
55
+ * Create model provider snapshot table
56
+ *
57
+ * `model_id` is the upstream model id and `provider_id` scopes it, so the same
58
+ * upstream id may appear under several providers in one tenant — each is an
59
+ * independent model addressed by `${providerId}/${modelId}`.
60
+ */
61
+ export const createModelProviderModelsTable: Migration = {
62
+ version: 182,
63
+ name: 'create_model_provider_models_table',
64
+ up: async (client: PoolClient) => {
65
+ await client.query(`
66
+ CREATE TABLE IF NOT EXISTS lattice_model_provider_models (
67
+ id VARCHAR(255) NOT NULL,
68
+ tenant_id VARCHAR(255) NOT NULL,
69
+ provider_id VARCHAR(255) NOT NULL,
70
+ model_id VARCHAR(512) NOT NULL,
71
+ display_name VARCHAR(255),
72
+ owned_by VARCHAR(255),
73
+ upstream_created TIMESTAMP,
74
+ enabled BOOLEAN NOT NULL DEFAULT TRUE,
75
+ raw JSONB,
76
+ discovered_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
77
+
78
+ PRIMARY KEY (tenant_id, provider_id, model_id)
79
+ )
80
+ `);
81
+
82
+ await client.query(`
83
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_id
84
+ ON lattice_model_provider_models(tenant_id)
85
+ `);
86
+
87
+ await client.query(`
88
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_model
89
+ ON lattice_model_provider_models(tenant_id, model_id)
90
+ `);
91
+
92
+ await client.query(`
93
+ CREATE INDEX IF NOT EXISTS idx_lattice_model_provider_models_tenant_provider
94
+ ON lattice_model_provider_models(tenant_id, provider_id)
95
+ `);
96
+ },
97
+ down: async (client: PoolClient) => {
98
+ await client.query('DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_provider');
99
+ await client.query('DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_model');
100
+ await client.query('DROP INDEX IF EXISTS idx_lattice_model_provider_models_tenant_id');
101
+ await client.query('DROP TABLE IF EXISTS lattice_model_provider_models');
102
+ },
103
+ };
104
+
105
+ /**
106
+ * Add `api_style` to an already-created providers table.
107
+ *
108
+ * `createModelProvidersTable` (v181) originally shipped without this column and
109
+ * gained it in a later edit. Migrations are recorded by name and never re-run,
110
+ * so any database that had already applied v181 kept a table without the column
111
+ * and every read failed on the missing field. Editing an applied migration does
112
+ * not update the databases that already ran it — this patch is what does.
113
+ *
114
+ * `IF NOT EXISTS` keeps it a no-op for databases created after the column moved
115
+ * into v181.
116
+ */
117
+ export const addModelProviderApiStyle: Migration = {
118
+ version: 183,
119
+ name: 'add_model_provider_api_style',
120
+ up: async (client: PoolClient) => {
121
+ await client.query(`
122
+ ALTER TABLE lattice_model_providers
123
+ ADD COLUMN IF NOT EXISTS api_style VARCHAR(32) NOT NULL DEFAULT 'chat-completions'
124
+ `);
125
+ },
126
+ down: async (client: PoolClient) => {
127
+ await client.query(`
128
+ ALTER TABLE lattice_model_providers DROP COLUMN IF EXISTS api_style
129
+ `);
130
+ },
131
+ };
132
+
133
+ /**
134
+ * Record that an upstream stopped offering a model, without deleting the row.
135
+ *
136
+ * Syncing used to have only two outcomes for a model the upstream dropped:
137
+ * remove it, or leave it looking identical to one the tenant switched off.
138
+ * Removing it silently breaks any agent or eval config that references the key;
139
+ * leaving it makes the two states indistinguishable. This column is the third
140
+ * option — the row stays and says why it is off, and the tenant decides when to
141
+ * delete it.
142
+ */
143
+ export const addModelProviderModelStale: Migration = {
144
+ version: 184,
145
+ name: 'add_model_provider_model_stale',
146
+ up: async (client: PoolClient) => {
147
+ await client.query(`
148
+ ALTER TABLE lattice_model_provider_models
149
+ ADD COLUMN IF NOT EXISTS stale BOOLEAN NOT NULL DEFAULT FALSE
150
+ `);
151
+ },
152
+ down: async (client: PoolClient) => {
153
+ await client.query(`
154
+ ALTER TABLE lattice_model_provider_models DROP COLUMN IF EXISTS stale
155
+ `);
156
+ },
157
+ };