@axiom-lattice/pg-stores 3.4.0 → 4.0.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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +45 -0
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +124 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +110 -18
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/PGVectorStoreProvider.ts +16 -5
- package/src/__tests__/AgentRunRetention.migrations.test.ts +3 -1
- package/src/__tests__/PostgreSQLChannelReplyJobStore.test.ts +4 -2
- package/src/__tests__/eval-task-id.test.ts +14 -3
- package/src/createPgStoreConfig.ts +2 -0
- package/src/migrations/eval_migrations.ts +19 -0
- package/src/migrations/model_provider_migrations.ts +38 -0
- package/src/migrations/task_migration.ts +17 -0
- package/src/stores/PostgreSQLAgentRunLedger.ts +15 -4
- package/src/stores/PostgreSQLEvalStore.ts +17 -4
- package/src/stores/PostgreSQLModelProviderStore.ts +28 -14
- package/src/stores/PostgreSQLTaskStore.ts +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.1",
|
|
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": "
|
|
29
|
-
"@axiom-lattice/protocols": "
|
|
28
|
+
"@axiom-lattice/core": "10.0.1",
|
|
29
|
+
"@axiom-lattice/protocols": "6.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/jest": "^29.5.14",
|
|
@@ -9,7 +9,11 @@ import { Pool } from "pg";
|
|
|
9
9
|
import { PGVectorStore } from "@langchain/community/vectorstores/pgvector";
|
|
10
10
|
import { Document } from "@langchain/core/documents";
|
|
11
11
|
import { embeddingsLatticeManager } from "@axiom-lattice/core";
|
|
12
|
-
import
|
|
12
|
+
import {
|
|
13
|
+
EMBEDDING_DEFAULT_KEY,
|
|
14
|
+
type VectorStoreProvider,
|
|
15
|
+
type VectorStoreCreateParams,
|
|
16
|
+
} from "@axiom-lattice/protocols";
|
|
13
17
|
import type { DocumentInterface } from "@langchain/core/documents";
|
|
14
18
|
|
|
15
19
|
function safeTableName(name: string): string {
|
|
@@ -31,15 +35,22 @@ export class PGVectorStoreProvider implements VectorStoreProvider {
|
|
|
31
35
|
async create(params: VectorStoreCreateParams): Promise<any> {
|
|
32
36
|
const { tableName, embeddingKey } = params;
|
|
33
37
|
const tenantId = params.tenantId ?? "default";
|
|
38
|
+
const requestedKey = embeddingKey || EMBEDDING_DEFAULT_KEY;
|
|
34
39
|
|
|
35
40
|
// Tenant first, deployment second — the same order every model resolver
|
|
36
|
-
// uses. A
|
|
37
|
-
//
|
|
41
|
+
// uses. A concrete key that misses falls back to the tenant's embedding
|
|
42
|
+
// default, then to the deployment registration.
|
|
38
43
|
const embeddings =
|
|
39
44
|
(await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
|
|
40
45
|
tenantId,
|
|
41
|
-
|
|
46
|
+
requestedKey
|
|
42
47
|
)) ??
|
|
48
|
+
(requestedKey === EMBEDDING_DEFAULT_KEY
|
|
49
|
+
? undefined
|
|
50
|
+
: await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
|
|
51
|
+
tenantId,
|
|
52
|
+
EMBEDDING_DEFAULT_KEY
|
|
53
|
+
)) ??
|
|
43
54
|
(await embeddingsLatticeManager.getEmbeddingsClientWithTenant(
|
|
44
55
|
tenantId,
|
|
45
56
|
"default"
|
|
@@ -48,7 +59,7 @@ export class PGVectorStoreProvider implements VectorStoreProvider {
|
|
|
48
59
|
if (!embeddings) {
|
|
49
60
|
throw new Error(
|
|
50
61
|
`No embedding model "${embeddingKey}" registered for tenant "${tenantId}", ` +
|
|
51
|
-
`and no deployment default. Configure one in the model
|
|
62
|
+
`and no tenant or deployment default. Configure one in the model service panel.`
|
|
52
63
|
);
|
|
53
64
|
}
|
|
54
65
|
|
|
@@ -34,7 +34,9 @@ describe("agent run retention migration", () => {
|
|
|
34
34
|
(name) => name === createAgentRunRetentionMigration.name,
|
|
35
35
|
),
|
|
36
36
|
).toHaveLength(1);
|
|
37
|
-
|
|
37
|
+
// Registration order is not the apply order — the manager sorts by version —
|
|
38
|
+
// so assert presence rather than that this is the last line registered.
|
|
39
|
+
expect(registeredMigrationNames).toContain(
|
|
38
40
|
agentRunProjectTaskRunningIndexMigration.name,
|
|
39
41
|
);
|
|
40
42
|
});
|
|
@@ -19,7 +19,9 @@ function input(
|
|
|
19
19
|
channelInstallationId: "install-1",
|
|
20
20
|
replyTarget: { chatId: "chat-1" },
|
|
21
21
|
inputMessageId: "input-1",
|
|
22
|
-
|
|
22
|
+
// Well in the past: the store compares against the database clock, which
|
|
23
|
+
// can trail the Node process clock by a few milliseconds.
|
|
24
|
+
availableAt: Date.now() - 60_000,
|
|
23
25
|
...overrides,
|
|
24
26
|
};
|
|
25
27
|
}
|
|
@@ -194,7 +196,7 @@ maybeDescribe("PostgreSQLChannelReplyJobStore (PostgreSQL)", () => {
|
|
|
194
196
|
store.reschedule(created.tenantId, created.runId, "owner-b", "nope", Date.now(), 1),
|
|
195
197
|
).resolves.toBeNull();
|
|
196
198
|
|
|
197
|
-
const availableAt = Date.now() -
|
|
199
|
+
const availableAt = Date.now() - 60_000;
|
|
198
200
|
const rescheduled = await store.reschedule(
|
|
199
201
|
created.tenantId,
|
|
200
202
|
created.runId,
|
|
@@ -6,16 +6,27 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { describe, expect, it, jest } from "@jest/globals";
|
|
9
|
-
import { addTaskIdToEvalRuns, evalMigrations } from "../migrations/eval_migrations";
|
|
9
|
+
import { addCancelRequestedToEvalRuns, addTaskIdToEvalRuns, evalMigrations } from "../migrations/eval_migrations";
|
|
10
10
|
|
|
11
11
|
describe("add_task_id_to_eval_runs migration", () => {
|
|
12
12
|
it("is registered in evalMigrations", () => {
|
|
13
13
|
expect(evalMigrations.map((m) => m.name)).toContain("add_task_id_to_eval_runs");
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
-
it("
|
|
16
|
+
it("keeps task_id before the cancellation migration", () => {
|
|
17
17
|
const versions = evalMigrations.map((m) => m.version);
|
|
18
|
-
expect(
|
|
18
|
+
expect(addCancelRequestedToEvalRuns.version).toBe(Math.max(...versions));
|
|
19
|
+
expect(addTaskIdToEvalRuns.version).toBeLessThan(addCancelRequestedToEvalRuns.version);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("emits the cooperative cancel column in up() and down()", async () => {
|
|
23
|
+
const upQueries: string[] = [];
|
|
24
|
+
await addCancelRequestedToEvalRuns.up({ query: jest.fn(async (sql: string) => { upQueries.push(sql); }) } as never);
|
|
25
|
+
expect(upQueries.some((q) => q.includes("cancel_requested BOOLEAN NOT NULL DEFAULT FALSE"))).toBe(true);
|
|
26
|
+
|
|
27
|
+
const downQueries: string[] = [];
|
|
28
|
+
await addCancelRequestedToEvalRuns.down({ query: jest.fn(async (sql: string) => { downQueries.push(sql); }) } as never);
|
|
29
|
+
expect(downQueries.some((q) => q.includes("DROP COLUMN IF EXISTS cancel_requested"))).toBe(true);
|
|
19
30
|
});
|
|
20
31
|
|
|
21
32
|
it("emits ADD COLUMN task_id in up()", async () => {
|
|
@@ -109,6 +109,7 @@ import {
|
|
|
109
109
|
addModelProviderModelExtra,
|
|
110
110
|
addModelProviderModelDefault,
|
|
111
111
|
addModelProviderModelKind,
|
|
112
|
+
addModelProviderKindDefault,
|
|
112
113
|
} from "./migrations/model_provider_migrations";
|
|
113
114
|
import { PostgreSQLCapabilityBundleStore } from "./stores/PostgreSQLCapabilityBundleStore";
|
|
114
115
|
import { PostgreSQLProjectRoomStore } from "./stores/PostgreSQLProjectRoomStore";
|
|
@@ -222,6 +223,7 @@ export async function createPgStoreConfig(connectionString: string) {
|
|
|
222
223
|
mm.register(createAgentRunRetentionMigration); // v201
|
|
223
224
|
mm.register(createAgentRunProjectTaskMigration); // v203
|
|
224
225
|
mm.register(agentRunProjectTaskRunningIndexMigration); // v204
|
|
226
|
+
mm.register(addModelProviderKindDefault); // v207
|
|
225
227
|
|
|
226
228
|
await mm.migrate();
|
|
227
229
|
|
|
@@ -381,6 +381,24 @@ export const addTaskIdToEvalRuns: Migration = {
|
|
|
381
381
|
},
|
|
382
382
|
};
|
|
383
383
|
|
|
384
|
+
|
|
385
|
+
/** Cooperative cancellation flag for running eval runs. */
|
|
386
|
+
export const addCancelRequestedToEvalRuns: Migration = {
|
|
387
|
+
version: 205,
|
|
388
|
+
name: "add_cancel_requested_to_eval_runs",
|
|
389
|
+
up: async (client: PoolClient) => {
|
|
390
|
+
await client.query(`
|
|
391
|
+
ALTER TABLE lattice_eval_runs
|
|
392
|
+
ADD COLUMN IF NOT EXISTS cancel_requested BOOLEAN NOT NULL DEFAULT FALSE
|
|
393
|
+
`);
|
|
394
|
+
},
|
|
395
|
+
down: async (client: PoolClient) => {
|
|
396
|
+
await client.query(`
|
|
397
|
+
ALTER TABLE lattice_eval_runs DROP COLUMN IF EXISTS cancel_requested
|
|
398
|
+
`);
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
|
|
384
402
|
/** All eval migrations in version order */
|
|
385
403
|
export const evalMigrations: Migration[] = [
|
|
386
404
|
createEvalProjectsTable,
|
|
@@ -394,4 +412,5 @@ export const evalMigrations: Migration[] = [
|
|
|
394
412
|
addInterruptPolicyToEvalCases,
|
|
395
413
|
enforceUniqueEvalProjectName,
|
|
396
414
|
addTaskIdToEvalRuns,
|
|
415
|
+
addCancelRequestedToEvalRuns,
|
|
397
416
|
];
|
|
@@ -256,3 +256,41 @@ export const addModelProviderModelKind: Migration = {
|
|
|
256
256
|
`);
|
|
257
257
|
},
|
|
258
258
|
};
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* One default per tenant **per kind**.
|
|
262
|
+
*
|
|
263
|
+
* v192's index was tenant-wide, so a tenant could have a default chat model
|
|
264
|
+
* *or* a default embedding model, never both. Each kind publishes its own
|
|
265
|
+
* alias key (`default`, `default:embedding`, `default:asr`), so the uniqueness
|
|
266
|
+
* constraint has to be per kind too.
|
|
267
|
+
*
|
|
268
|
+
* Existing rows satisfy the wider index unchanged: before this, at most one
|
|
269
|
+
* row per tenant could be the default.
|
|
270
|
+
*
|
|
271
|
+
* Note the `down` migration can legitimately fail once a tenant has one
|
|
272
|
+
* default per kind — the narrower index cannot represent that state. That is a
|
|
273
|
+
* deliberate one-way step.
|
|
274
|
+
*/
|
|
275
|
+
export const addModelProviderKindDefault: Migration = {
|
|
276
|
+
version: 207,
|
|
277
|
+
name: 'add_model_provider_kind_default',
|
|
278
|
+
up: async (client: PoolClient) => {
|
|
279
|
+
await client.query(`
|
|
280
|
+
DROP INDEX IF EXISTS uk_lattice_model_provider_models_tenant_default
|
|
281
|
+
`);
|
|
282
|
+
await client.query(`
|
|
283
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uk_lattice_model_provider_models_tenant_kind_default
|
|
284
|
+
ON lattice_model_provider_models (tenant_id, kind) WHERE is_default
|
|
285
|
+
`);
|
|
286
|
+
},
|
|
287
|
+
down: async (client: PoolClient) => {
|
|
288
|
+
await client.query(`
|
|
289
|
+
DROP INDEX IF EXISTS uk_lattice_model_provider_models_tenant_kind_default
|
|
290
|
+
`);
|
|
291
|
+
await client.query(`
|
|
292
|
+
CREATE UNIQUE INDEX IF NOT EXISTS uk_lattice_model_provider_models_tenant_default
|
|
293
|
+
ON lattice_model_provider_models (tenant_id) WHERE is_default
|
|
294
|
+
`);
|
|
295
|
+
},
|
|
296
|
+
};
|
|
@@ -112,6 +112,22 @@ export const addTaskDependenciesGinIndex: Migration = {
|
|
|
112
112
|
},
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
/** Index task execution threads for ownership checks on isolated worker threads. */
|
|
116
|
+
export const addTaskContextThreadIndex: Migration = {
|
|
117
|
+
version: 206,
|
|
118
|
+
name: "add_task_context_thread_index",
|
|
119
|
+
up: async (client) => {
|
|
120
|
+
await client.query(`
|
|
121
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_context_thread
|
|
122
|
+
ON lattice_tasks (tenant_id, (context->>'thread_id'))
|
|
123
|
+
WHERE context->>'thread_id' IS NOT NULL
|
|
124
|
+
`);
|
|
125
|
+
},
|
|
126
|
+
down: async (client) => {
|
|
127
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_tasks_context_thread");
|
|
128
|
+
},
|
|
129
|
+
};
|
|
130
|
+
|
|
115
131
|
/** All task migrations in version order */
|
|
116
132
|
export const taskMigrations: Migration[] = [
|
|
117
133
|
createTasksTable,
|
|
@@ -119,4 +135,5 @@ export const taskMigrations: Migration[] = [
|
|
|
119
135
|
addTaskProjectFieldsMigration,
|
|
120
136
|
addFilesToTasks,
|
|
121
137
|
addTaskDependenciesGinIndex,
|
|
138
|
+
addTaskContextThreadIndex,
|
|
122
139
|
];
|
|
@@ -274,7 +274,10 @@ export class PostgreSQLAgentRunLedger implements IAgentRunLedger {
|
|
|
274
274
|
const inputMessageIds = input.inputMessageIds
|
|
275
275
|
? normalizeAgentRunMessageIds(input.inputMessageIds)
|
|
276
276
|
: extractAgentRunInputMessageIds(input.input);
|
|
277
|
-
|
|
277
|
+
// Default to the database clock. Mixing Node's `Date.now()` with the
|
|
278
|
+
// `available_at <= now()` claim predicate makes freshly created rows
|
|
279
|
+
// briefly unclaimable whenever the two clocks drift by a few milliseconds.
|
|
280
|
+
const availableAt = input.availableAt ?? null;
|
|
278
281
|
const limit = input.maxActiveRunsForTenant;
|
|
279
282
|
const client = await this.pool.connect();
|
|
280
283
|
try {
|
|
@@ -333,7 +336,10 @@ export class PostgreSQLAgentRunLedger implements IAgentRunLedger {
|
|
|
333
336
|
available_at, created_at, updated_at, task_id)
|
|
334
337
|
VALUES ($1, $2, $3, $4, $5, $6, 'pending', $7, NULL, NULL, $8, 0,
|
|
335
338
|
$9::jsonb, $10::jsonb, $11::jsonb, $12::jsonb, $13,
|
|
336
|
-
|
|
339
|
+
COALESCE(
|
|
340
|
+
to_timestamp($14::double precision / 1000.0),
|
|
341
|
+
date_trunc('milliseconds', now())
|
|
342
|
+
),
|
|
337
343
|
date_trunc('milliseconds', now()),
|
|
338
344
|
date_trunc('milliseconds', now()), $15)
|
|
339
345
|
RETURNING *`,
|
|
@@ -971,14 +977,19 @@ export class PostgreSQLAgentRunLedger implements IAgentRunLedger {
|
|
|
971
977
|
options?: SettleExpiredRunOptions,
|
|
972
978
|
): Promise<boolean> {
|
|
973
979
|
if (outcome === "pending") {
|
|
974
|
-
|
|
980
|
+
// Same clock rule as createPendingRun: only explicit backoff timestamps
|
|
981
|
+
// are caller-supplied; the default must come from the database.
|
|
982
|
+
const availableAt = options?.availableAt ?? null;
|
|
975
983
|
const result = await this.pool.query(
|
|
976
984
|
`UPDATE lattice_agent_runs
|
|
977
985
|
SET status = 'pending',
|
|
978
986
|
owner = NULL,
|
|
979
987
|
lease_expires_at = NULL,
|
|
980
988
|
started_at = NULL,
|
|
981
|
-
available_at =
|
|
989
|
+
available_at = COALESCE(
|
|
990
|
+
to_timestamp($2::double precision / 1000.0),
|
|
991
|
+
date_trunc('milliseconds', now())
|
|
992
|
+
),
|
|
982
993
|
finished_at = NULL,
|
|
983
994
|
updated_at = now()
|
|
984
995
|
WHERE run_id = $1
|
|
@@ -180,6 +180,7 @@ export class PostgreSQLEvalStore implements EvalStore {
|
|
|
180
180
|
interruptedCases: (row.interrupted_cases as number) ?? 0,
|
|
181
181
|
avgScore: (row.avg_score as number) ?? 0,
|
|
182
182
|
error: row.error as string | undefined,
|
|
183
|
+
cancelRequested: (row.cancel_requested as boolean) ?? false,
|
|
183
184
|
holdout: row.holdout as boolean | undefined,
|
|
184
185
|
envProjectId: row.env_project_id as string | undefined,
|
|
185
186
|
envWorkspaceId: row.env_workspace_id as string | undefined,
|
|
@@ -610,7 +611,7 @@ export class PostgreSQLEvalStore implements EvalStore {
|
|
|
610
611
|
`SELECT id, project_id, tenant_id,
|
|
611
612
|
status, concurrency, total_cases,
|
|
612
613
|
passed_cases, failed_cases, interrupted_cases, avg_score,
|
|
613
|
-
error, holdout, env_project_id, env_workspace_id, task_id, created_at, started_at, completed_at
|
|
614
|
+
error, holdout, env_project_id, env_workspace_id, task_id, cancel_requested, created_at, started_at, completed_at
|
|
614
615
|
FROM lattice_eval_runs
|
|
615
616
|
WHERE ${conditions.join(" AND ")}
|
|
616
617
|
ORDER BY created_at DESC`,
|
|
@@ -632,7 +633,7 @@ export class PostgreSQLEvalStore implements EvalStore {
|
|
|
632
633
|
`SELECT id, project_id, tenant_id,
|
|
633
634
|
status, concurrency, total_cases,
|
|
634
635
|
passed_cases, failed_cases, interrupted_cases, avg_score,
|
|
635
|
-
error, holdout, env_project_id, env_workspace_id, task_id, created_at, started_at, completed_at
|
|
636
|
+
error, holdout, env_project_id, env_workspace_id, task_id, cancel_requested, created_at, started_at, completed_at
|
|
636
637
|
FROM lattice_eval_runs
|
|
637
638
|
WHERE id = $1 AND tenant_id = $2`,
|
|
638
639
|
[id, tenantId]
|
|
@@ -662,7 +663,7 @@ export class PostgreSQLEvalStore implements EvalStore {
|
|
|
662
663
|
RETURNING id, project_id, tenant_id,
|
|
663
664
|
status, concurrency, total_cases,
|
|
664
665
|
passed_cases, failed_cases, interrupted_cases, avg_score,
|
|
665
|
-
error, holdout, env_project_id, env_workspace_id, task_id, created_at, started_at, completed_at`,
|
|
666
|
+
error, holdout, env_project_id, env_workspace_id, task_id, cancel_requested, created_at, started_at, completed_at`,
|
|
666
667
|
[
|
|
667
668
|
actualId,
|
|
668
669
|
projectId,
|
|
@@ -722,12 +723,24 @@ export class PostgreSQLEvalStore implements EvalStore {
|
|
|
722
723
|
RETURNING id, project_id, tenant_id,
|
|
723
724
|
status, concurrency, total_cases,
|
|
724
725
|
passed_cases, failed_cases, interrupted_cases, avg_score,
|
|
725
|
-
error, holdout, env_project_id, env_workspace_id, task_id, created_at, started_at, completed_at`,
|
|
726
|
+
error, holdout, env_project_id, env_workspace_id, task_id, cancel_requested, created_at, started_at, completed_at`,
|
|
726
727
|
vals
|
|
727
728
|
);
|
|
728
729
|
return rows.length ? this.mapRowToRun(rows[0] as unknown as Record<string, unknown>) : null;
|
|
729
730
|
}
|
|
730
731
|
|
|
732
|
+
/** Request cooperative cancellation for a running evaluation. */
|
|
733
|
+
async requestCancel(tenantId: string, id: string): Promise<boolean> {
|
|
734
|
+
await this.ensureInitialized();
|
|
735
|
+
const { rowCount } = await this.pool.query(
|
|
736
|
+
`UPDATE lattice_eval_runs
|
|
737
|
+
SET cancel_requested = TRUE
|
|
738
|
+
WHERE tenant_id = $1 AND id = $2 AND status = 'running'`,
|
|
739
|
+
[tenantId, id],
|
|
740
|
+
);
|
|
741
|
+
return (rowCount ?? 0) > 0;
|
|
742
|
+
}
|
|
743
|
+
|
|
731
744
|
/** Delete a run and its results */
|
|
732
745
|
async deleteRun(tenantId: string, id: string): Promise<boolean> {
|
|
733
746
|
await this.ensureInitialized();
|
|
@@ -26,6 +26,7 @@ import {
|
|
|
26
26
|
addModelProviderModelExtra,
|
|
27
27
|
addModelProviderModelDefault,
|
|
28
28
|
addModelProviderModelKind,
|
|
29
|
+
addModelProviderKindDefault,
|
|
29
30
|
} from '../migrations/model_provider_migrations';
|
|
30
31
|
import { encrypt, safeDecrypt } from '@axiom-lattice/core';
|
|
31
32
|
|
|
@@ -148,6 +149,7 @@ export class PostgreSQLModelProviderStore implements ModelProviderStore {
|
|
|
148
149
|
this.migrationManager.register(addModelProviderModelExtra); // v191
|
|
149
150
|
this.migrationManager.register(addModelProviderModelDefault); // v192
|
|
150
151
|
this.migrationManager.register(addModelProviderModelKind); // v193
|
|
152
|
+
this.migrationManager.register(addModelProviderKindDefault); // v207
|
|
151
153
|
|
|
152
154
|
// Auto-migrate by default
|
|
153
155
|
if (options.autoMigrate !== false) {
|
|
@@ -610,30 +612,42 @@ export class PostgreSQLModelProviderStore implements ModelProviderStore {
|
|
|
610
612
|
(model) => model.kind ?? currentKind.get(model.id) ?? "chat"
|
|
611
613
|
);
|
|
612
614
|
|
|
613
|
-
//
|
|
615
|
+
// Which kinds this write names a default for.
|
|
614
616
|
//
|
|
615
|
-
// The unique index
|
|
617
|
+
// The unique index is per *(tenant, kind)*, while this method writes one
|
|
616
618
|
// provider. Two consequences:
|
|
617
619
|
//
|
|
618
|
-
// 1. A swap (A was
|
|
619
|
-
// mid-statement — PostgreSQL checks a unique index per row, so
|
|
620
|
-
// it fails would depend on which row the batch happened to
|
|
621
|
-
// first. Clearing
|
|
620
|
+
// 1. A swap (A was kind K's default, B now is) would see two true rows of
|
|
621
|
+
// kind K mid-statement — PostgreSQL checks a unique index per row, so
|
|
622
|
+
// whether it fails would depend on which row the batch happened to
|
|
623
|
+
// process first. Clearing that kind before the upsert makes order
|
|
622
624
|
// irrelevant.
|
|
623
|
-
// 2. Once cleared, an omitted flag must resolve to `false
|
|
624
|
-
// keeping it would restore the old default
|
|
625
|
-
|
|
626
|
-
|
|
625
|
+
// 2. Once cleared, an omitted flag must resolve to `false` for the kinds
|
|
626
|
+
// this write names — keeping it would restore the old default
|
|
627
|
+
// alongside the new one. Other kinds keep their stored flag, so a chat
|
|
628
|
+
// write cannot drop the tenant's embedding default.
|
|
629
|
+
const namedDefaultKinds = new Set<ModelKind>();
|
|
630
|
+
rows.forEach((model, index) => {
|
|
631
|
+
if (model.isDefault === true) {
|
|
632
|
+
namedDefaultKinds.add(effectiveKind[index] as ModelKind);
|
|
633
|
+
}
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
for (const kind of namedDefaultKinds) {
|
|
627
637
|
await client.query(
|
|
628
|
-
`UPDATE lattice_model_provider_models
|
|
629
|
-
|
|
638
|
+
`UPDATE lattice_model_provider_models
|
|
639
|
+
SET is_default = FALSE
|
|
640
|
+
WHERE tenant_id = $1 AND kind = $2 AND is_default`,
|
|
641
|
+
[tenantId, kind]
|
|
630
642
|
);
|
|
631
643
|
}
|
|
632
644
|
|
|
633
645
|
const effectiveIsDefault = rows.map(
|
|
634
|
-
(model) =>
|
|
646
|
+
(model, index) =>
|
|
635
647
|
model.isDefault ??
|
|
636
|
-
(
|
|
648
|
+
(namedDefaultKinds.has(effectiveKind[index] as ModelKind)
|
|
649
|
+
? false
|
|
650
|
+
: currentIsDefault.get(model.id)) ??
|
|
637
651
|
false
|
|
638
652
|
);
|
|
639
653
|
|
|
@@ -283,6 +283,10 @@ export class PostgreSQLTaskStore implements TaskStore {
|
|
|
283
283
|
conditions.push(`priority = $${paramIndex++}`);
|
|
284
284
|
params.push(filter.priority);
|
|
285
285
|
}
|
|
286
|
+
if (filter.threadId) {
|
|
287
|
+
conditions.push(`context->>'thread_id' = $${paramIndex++}`);
|
|
288
|
+
params.push(filter.threadId);
|
|
289
|
+
}
|
|
286
290
|
if (filter.workspaceId) {
|
|
287
291
|
conditions.push(`workspace_id = $${paramIndex++}`);
|
|
288
292
|
params.push(filter.workspaceId);
|