or3-provider-sqlite 0.0.13 → 0.0.14
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/README.md +15 -0
- package/dist/module.json +1 -1
- package/dist/runtime/server/background-jobs/sqlite-provider.js +38 -5
- package/dist/runtime/server/db/migrate.js +7 -1
- package/dist/runtime/server/db/migrations/021_background_request_usage.d.ts +4 -0
- package/dist/runtime/server/db/migrations/021_background_request_usage.js +7 -0
- package/dist/runtime/server/db/migrations/022_canonical_chat_reader.d.ts +4 -0
- package/dist/runtime/server/db/migrations/022_canonical_chat_reader.js +26 -0
- package/dist/runtime/server/db/migrations/023_legacy_chat_seek_index.d.ts +4 -0
- package/dist/runtime/server/db/migrations/023_legacy_chat_seek_index.js +24 -0
- package/dist/runtime/server/db/schema.d.ts +1 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.d.ts +4 -0
- package/dist/runtime/server/sync/sqlite-sync-gateway-adapter.js +50 -0
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -227,6 +227,13 @@ bun run type-check # TypeScript validation
|
|
|
227
227
|
bun run build # build for distribution
|
|
228
228
|
```
|
|
229
229
|
|
|
230
|
+
The normal test suite runs in a provider-only checkout using hash-verified,
|
|
231
|
+
byte-for-byte host runtime fixtures. `bun run test:host-integration` reruns the
|
|
232
|
+
same background-job, canonical-history and registration cases against a prepared
|
|
233
|
+
sibling `../or3-chat` at the exact reviewed source tree. It fails on missing or
|
|
234
|
+
mismatched source rather than falling back to fixtures. See the
|
|
235
|
+
[fixture provenance and prerequisites](test/fixtures/host/README.md).
|
|
236
|
+
|
|
230
237
|
## Compatibility
|
|
231
238
|
|
|
232
239
|
- Works with multiple auth providers (`basic-auth`, `clerk`, or custom)
|
|
@@ -274,6 +281,14 @@ or failed builds fall back to installed packages with a warning.
|
|
|
274
281
|
`OR3_LOCAL_PROVIDERS=false` disables local selection. Production builds use the
|
|
275
282
|
installed package, so local development does not publish these changes.
|
|
276
283
|
|
|
284
|
+
### Canonical compaction history reader (unreleased)
|
|
285
|
+
|
|
286
|
+
`canonicalChatHistory: 'v1'` and `readChatHistory` read current `s_threads`/`s_messages`, with explicit workspace membership (owner/editor/viewer), bounded by-ID queries and canonical `(index, order_key, id)` keyset pages. Migration 022 adds transactional per-thread revision triggers. Migration 023 matches the index to legacy missing-order-key normalization and preserves the existing orphan-row cleanup lifecycle; revision bookkeeping does not add a foreign key to otherwise independent sync rows. Current-chat tool result writes do not invalidate ancestor search cursors. The reader never consults retained sync logs. D1 omits the capability.
|
|
287
|
+
|
|
288
|
+
The additive local qualification command is `bun run build` followed by `bunx vitest run --config vitest.canonical-history.config.ts`. It checks committed current host contract bytes without replacing the older pinned fixture. The current-host lane passes 119 cases across three files, including the real gateway owner and source-built artifact reads after change-log deletion, legacy tie paging in both directions, bounded queries, revision changes, cancellation, revoked membership and an index plan without a temporary sort. These checks do not qualify a deployed rollout, second-client/background reconciliation or released pins. This compaction capability is not yet included in a published package.
|
|
289
|
+
|
|
290
|
+
The canonical-history qualification lane defaults to a sibling `../or3-chat` checkout. For an isolated host worktree, set `OR3_CANONICAL_HOST_ROOT=/absolute/path/to/or3-chat` before the command. Install the host dependencies too; the two-client fixture resolves its IndexedDB test dependency from that checkout. Its contract-byte checks fail closed on uncommitted changes; this does not replace the pinned host fixture or certify deployed/released provider behavior.
|
|
291
|
+
|
|
277
292
|
## Workspace Files and Trash admission
|
|
278
293
|
|
|
279
294
|
Native synchronous adapters advertise workspace-item capability v1. Canonical
|
package/dist/module.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getJobConfig } from "~~/server/utils/background-jobs/store";
|
|
2
|
+
import { readRequestUsage } from "~~/shared/chat/compaction";
|
|
2
3
|
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { d1All, d1Run } from "../db/d1.js";
|
|
4
5
|
import { getRawDb, isD1Driver } from "../db/kysely.js";
|
|
@@ -9,6 +10,13 @@ function parseJson(value) {
|
|
|
9
10
|
function json(value) {
|
|
10
11
|
return value === void 0 ? null : JSON.stringify(value);
|
|
11
12
|
}
|
|
13
|
+
function readStoredUsage(value) {
|
|
14
|
+
try {
|
|
15
|
+
return readRequestUsage(parseJson(value));
|
|
16
|
+
} catch {
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
12
20
|
function toJob(row) {
|
|
13
21
|
return {
|
|
14
22
|
id: row.id,
|
|
@@ -29,6 +37,7 @@ function toJob(row) {
|
|
|
29
37
|
completedAt: row.completed_at ?? void 0,
|
|
30
38
|
error: row.error ?? void 0,
|
|
31
39
|
tool_calls: parseJson(row.tool_calls_json),
|
|
40
|
+
usage: readStoredUsage(row.usage_json),
|
|
32
41
|
workflow_state: parseJson(
|
|
33
42
|
row.workflow_state_json
|
|
34
43
|
),
|
|
@@ -326,12 +335,19 @@ export class SqliteBackgroundJobProvider {
|
|
|
326
335
|
sets.push("workflow_state_json = ?");
|
|
327
336
|
values.push(json(update.workflow_state));
|
|
328
337
|
}
|
|
338
|
+
const usage = readRequestUsage(update.usage);
|
|
339
|
+
if (usage) {
|
|
340
|
+
sets.push("usage_json = ?");
|
|
341
|
+
values.push(json(usage));
|
|
342
|
+
}
|
|
329
343
|
values.push(jobId);
|
|
330
344
|
let where = "id = ? AND status = 'streaming'";
|
|
331
345
|
if (update.leaseOwner) {
|
|
332
346
|
where += " AND lease_owner = ? AND lease_expires_at > ?";
|
|
333
347
|
values.push(update.leaseOwner);
|
|
334
348
|
values.push(Date.now());
|
|
349
|
+
} else {
|
|
350
|
+
where += " AND lease_owner IS NULL";
|
|
335
351
|
}
|
|
336
352
|
const changes = await run(
|
|
337
353
|
`UPDATE background_jobs SET ${sets.join(", ")} WHERE ${where}`,
|
|
@@ -353,8 +369,10 @@ export class SqliteBackgroundJobProvider {
|
|
|
353
369
|
values.push(jobId);
|
|
354
370
|
let where = "id = ? AND status = 'streaming'";
|
|
355
371
|
if (leaseOwner) {
|
|
356
|
-
where += " AND lease_owner = ?";
|
|
357
|
-
values.push(leaseOwner);
|
|
372
|
+
where += " AND lease_owner = ? AND lease_expires_at > ?";
|
|
373
|
+
values.push(leaseOwner, now);
|
|
374
|
+
} else {
|
|
375
|
+
where += " AND lease_owner IS NULL";
|
|
358
376
|
}
|
|
359
377
|
const changes = await run(
|
|
360
378
|
`UPDATE background_jobs SET status = ?, completed_at = ?, last_activity_at = ?,
|
|
@@ -403,6 +421,11 @@ export class SqliteBackgroundJobProvider {
|
|
|
403
421
|
reasoning = CASE WHEN attempts > 0
|
|
404
422
|
THEN COALESCE(json_extract(execution_json, '$.reasoningBase'), '')
|
|
405
423
|
ELSE reasoning END,
|
|
424
|
+
usage_json = CASE WHEN attempts > 0
|
|
425
|
+
THEN CASE WHEN json_type(execution_json, '$.normalizedToolState.requestUsage') = 'object'
|
|
426
|
+
THEN json_extract(execution_json, '$.normalizedToolState.requestUsage')
|
|
427
|
+
ELSE NULL END
|
|
428
|
+
ELSE usage_json END,
|
|
406
429
|
chunks_received = CASE WHEN attempts > 0 THEN 0 ELSE chunks_received END,
|
|
407
430
|
attempts = attempts + 1
|
|
408
431
|
WHERE id = ? AND status = 'streaming' AND execution_json IS NOT NULL
|
|
@@ -430,6 +453,11 @@ export class SqliteBackgroundJobProvider {
|
|
|
430
453
|
reasoning = CASE WHEN attempts > 0
|
|
431
454
|
THEN COALESCE(json_extract(execution_json, '$.reasoningBase'), '')
|
|
432
455
|
ELSE reasoning END,
|
|
456
|
+
usage_json = CASE WHEN attempts > 0
|
|
457
|
+
THEN CASE WHEN json_type(execution_json, '$.normalizedToolState.requestUsage') = 'object'
|
|
458
|
+
THEN json_extract(execution_json, '$.normalizedToolState.requestUsage')
|
|
459
|
+
ELSE NULL END
|
|
460
|
+
ELSE usage_json END,
|
|
433
461
|
chunks_received = CASE WHEN attempts > 0 THEN 0 ELSE chunks_received END,
|
|
434
462
|
attempts = attempts + 1
|
|
435
463
|
WHERE id = (
|
|
@@ -462,11 +490,13 @@ export class SqliteBackgroundJobProvider {
|
|
|
462
490
|
async updateJobExecution(jobId, execution, leaseOwner) {
|
|
463
491
|
return await run(
|
|
464
492
|
`UPDATE background_jobs SET execution_json = ?, last_activity_at = ?
|
|
465
|
-
WHERE id = ? AND status = 'streaming' AND lease_owner =
|
|
493
|
+
WHERE id = ? AND status = 'streaming' AND lease_owner = ?
|
|
494
|
+
AND lease_expires_at > ?`,
|
|
466
495
|
json(execution),
|
|
467
496
|
Date.now(),
|
|
468
497
|
jobId,
|
|
469
|
-
leaseOwner
|
|
498
|
+
leaseOwner,
|
|
499
|
+
Date.now()
|
|
470
500
|
) > 0;
|
|
471
501
|
}
|
|
472
502
|
async claimClientToolCall(jobId, userId, callId, claimToken, claimExpiresAt) {
|
|
@@ -521,6 +551,7 @@ export class SqliteBackgroundJobProvider {
|
|
|
521
551
|
snapshot.content,
|
|
522
552
|
snapshot.reasoning,
|
|
523
553
|
json(snapshot.toolCalls),
|
|
554
|
+
json(readRequestUsage(snapshot.usage)),
|
|
524
555
|
snapshot.error ?? null,
|
|
525
556
|
snapshot.completedAt,
|
|
526
557
|
snapshot.completedAt
|
|
@@ -530,10 +561,12 @@ export class SqliteBackgroundJobProvider {
|
|
|
530
561
|
if (leaseOwner) {
|
|
531
562
|
where += " AND lease_owner = ? AND lease_expires_at > ?";
|
|
532
563
|
values.push(leaseOwner, Date.now());
|
|
564
|
+
} else {
|
|
565
|
+
where += " AND lease_owner IS NULL";
|
|
533
566
|
}
|
|
534
567
|
return await run(
|
|
535
568
|
`UPDATE background_jobs SET status = ?, content = ?, reasoning = ?,
|
|
536
|
-
tool_calls_json = ?, error = ?, completed_at = ?,
|
|
569
|
+
tool_calls_json = ?, usage_json = COALESCE(?, usage_json), error = ?, completed_at = ?,
|
|
537
570
|
history_phase = 'finalization_pending',
|
|
538
571
|
last_activity_at = ?, lease_owner = NULL, lease_expires_at = NULL
|
|
539
572
|
WHERE ${where}`,
|
|
@@ -19,6 +19,9 @@ import * as m017 from "./migrations/017_background_jobs.js";
|
|
|
19
19
|
import * as m018 from "./migrations/018_background_admission_cancels.js";
|
|
20
20
|
import * as m019 from "./migrations/019_background_generation_history.js";
|
|
21
21
|
import * as m020 from "./migrations/020_background_generation_receipts.js";
|
|
22
|
+
import * as m021 from "./migrations/021_background_request_usage.js";
|
|
23
|
+
import * as m022 from "./migrations/022_canonical_chat_reader.js";
|
|
24
|
+
import * as m023 from "./migrations/023_legacy_chat_seek_index.js";
|
|
22
25
|
const REQUIRED_SCHEMA_TABLES = [
|
|
23
26
|
"users",
|
|
24
27
|
"auth_accounts",
|
|
@@ -68,7 +71,10 @@ const migrations = {
|
|
|
68
71
|
"017_background_jobs": m017,
|
|
69
72
|
"018_background_admission_cancels": m018,
|
|
70
73
|
"019_background_generation_history": m019,
|
|
71
|
-
"020_background_generation_receipts": m020
|
|
74
|
+
"020_background_generation_receipts": m020,
|
|
75
|
+
"021_background_request_usage": m021,
|
|
76
|
+
"022_canonical_chat_reader": m022,
|
|
77
|
+
"023_legacy_chat_seek_index": m023
|
|
72
78
|
};
|
|
73
79
|
class StaticMigrationProvider {
|
|
74
80
|
async getMigrations() {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
export async function up(db) {
|
|
3
|
+
await sql`CREATE TABLE IF NOT EXISTS chat_history_revisions (
|
|
4
|
+
workspace_id TEXT NOT NULL REFERENCES workspaces(id) ON DELETE CASCADE,
|
|
5
|
+
thread_id TEXT NOT NULL, value INTEGER NOT NULL DEFAULT 0,
|
|
6
|
+
PRIMARY KEY (workspace_id, thread_id))`.execute(db);
|
|
7
|
+
await sql`CREATE INDEX IF NOT EXISTS s_messages_history_order ON s_messages
|
|
8
|
+
(workspace_id, json_extract(data_json, '$.thread_id'), json_extract(data_json, '$.index'), json_extract(data_json, '$.order_key'), id)`.execute(db);
|
|
9
|
+
for (const table of ["s_threads", "s_messages"]) for (const event of ["INSERT", "UPDATE", "DELETE"]) {
|
|
10
|
+
const rowNames = event === "UPDATE" ? ["OLD", "NEW"] : [event === "DELETE" ? "OLD" : "NEW"];
|
|
11
|
+
const statements = rowNames.map((row) => {
|
|
12
|
+
const thread = table === "s_threads" ? `${row}.id` : `json_extract(${row}.data_json, '$.thread_id')`;
|
|
13
|
+
return `INSERT INTO chat_history_revisions (workspace_id, thread_id, value)
|
|
14
|
+
SELECT ${row}.workspace_id, ${thread}, 1 WHERE ${thread} IS NOT NULL
|
|
15
|
+
ON CONFLICT (workspace_id, thread_id) DO UPDATE SET value = chat_history_revisions.value + 1;`;
|
|
16
|
+
}).join("\n");
|
|
17
|
+
await sql.raw(`CREATE TRIGGER IF NOT EXISTS ${table}_history_${event.toLowerCase()} AFTER ${event} ON ${table} BEGIN ${statements} END`).execute(db);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
export async function down(db) {
|
|
21
|
+
for (const table of ["s_threads", "s_messages"]) for (const event of ["insert", "update", "delete"]) {
|
|
22
|
+
await sql.raw(`DROP TRIGGER IF EXISTS ${table}_history_${event}`).execute(db);
|
|
23
|
+
}
|
|
24
|
+
await sql`DROP INDEX IF EXISTS s_messages_history_order`.execute(db);
|
|
25
|
+
await sql`DROP TABLE IF EXISTS chat_history_revisions`.execute(db);
|
|
26
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { sql } from "kysely";
|
|
2
|
+
import { up as installCanonicalHistory } from "./022_canonical_chat_reader.js";
|
|
3
|
+
export async function up(db) {
|
|
4
|
+
for (const table of ["s_threads", "s_messages"]) for (const event of ["insert", "update", "delete"]) {
|
|
5
|
+
await sql.raw(`DROP TRIGGER IF EXISTS ${table}_history_${event}`).execute(db);
|
|
6
|
+
}
|
|
7
|
+
await sql`CREATE TABLE chat_history_revisions_v2 (
|
|
8
|
+
workspace_id TEXT NOT NULL, thread_id TEXT NOT NULL, value INTEGER NOT NULL DEFAULT 0,
|
|
9
|
+
PRIMARY KEY (workspace_id, thread_id))`.execute(db);
|
|
10
|
+
await sql`INSERT INTO chat_history_revisions_v2 SELECT workspace_id, thread_id, value FROM chat_history_revisions`.execute(db);
|
|
11
|
+
await sql`DROP TABLE chat_history_revisions`.execute(db);
|
|
12
|
+
await sql`ALTER TABLE chat_history_revisions_v2 RENAME TO chat_history_revisions`.execute(db);
|
|
13
|
+
await installCanonicalHistory(db);
|
|
14
|
+
await sql`DROP INDEX IF EXISTS s_messages_history_order`.execute(db);
|
|
15
|
+
await sql`CREATE INDEX s_messages_history_order ON s_messages
|
|
16
|
+
(workspace_id, json_extract(data_json, '$.thread_id'), json_extract(data_json, '$.index'),
|
|
17
|
+
COALESCE(json_extract(data_json, '$.order_key'), ''), id)`.execute(db);
|
|
18
|
+
}
|
|
19
|
+
export async function down(db) {
|
|
20
|
+
await sql`DROP INDEX IF EXISTS s_messages_history_order`.execute(db);
|
|
21
|
+
await sql`CREATE INDEX s_messages_history_order ON s_messages
|
|
22
|
+
(workspace_id, json_extract(data_json, '$.thread_id'), json_extract(data_json, '$.index'),
|
|
23
|
+
json_extract(data_json, '$.order_key'), id)`.execute(db);
|
|
24
|
+
}
|
|
@@ -207,6 +207,7 @@ export interface BackgroundJobsTable {
|
|
|
207
207
|
completed_at: number | null;
|
|
208
208
|
error: string | null;
|
|
209
209
|
tool_calls_json: string | null;
|
|
210
|
+
usage_json: string | null;
|
|
210
211
|
workflow_state_json: string | null;
|
|
211
212
|
execution_json: string | null;
|
|
212
213
|
idempotency_key: string | null;
|
|
@@ -9,20 +9,24 @@ import type { H3Event } from 'h3';
|
|
|
9
9
|
import type { CanonicalStorageQueryRequest, CanonicalStorageQueryResponse, SyncGatewayAdapter, SyncMaintenanceState, UploadIntentConsumptionRequest, UploadIntentReservationRequest } from '~~/server/sync/gateway/types';
|
|
10
10
|
import type { AdmitChatGenerationResult, CanonicalGenerationSnapshot, CanonicalHistoryActor, ChatGenerationAdmissionEnvelope, FinalizeChatGenerationResult } from '~~/shared/chat/background-history';
|
|
11
11
|
import type { PullRequest, PullResponse, PushBatch, PushResult, SnapshotRequest, SnapshotResponse } from '~~/shared/sync/types';
|
|
12
|
+
import { type CanonicalChatQuery, type CanonicalChatReadResult } from '~~/shared/chat/history-reader';
|
|
12
13
|
export declare class SqliteSyncGatewayAdapter implements SyncGatewayAdapter {
|
|
13
14
|
id: string;
|
|
14
15
|
get capabilities(): {
|
|
15
16
|
readonly snapshotBootstrap: "snapshot-v1";
|
|
16
17
|
readonly historyRetention: "snapshot-v1";
|
|
17
18
|
readonly backgroundGenerationHistory?: undefined;
|
|
19
|
+
readonly canonicalChatHistory?: undefined;
|
|
18
20
|
readonly workspaceItems?: undefined;
|
|
19
21
|
} | {
|
|
20
22
|
readonly snapshotBootstrap: "snapshot-v1";
|
|
21
23
|
readonly historyRetention: "snapshot-v1";
|
|
22
24
|
readonly backgroundGenerationHistory: "v1";
|
|
25
|
+
readonly canonicalChatHistory: "v1";
|
|
23
26
|
readonly workspaceItems: "v1";
|
|
24
27
|
};
|
|
25
28
|
private get db();
|
|
29
|
+
readChatHistory(actor: CanonicalHistoryActor, query: CanonicalChatQuery, signal?: AbortSignal): Promise<CanonicalChatReadResult>;
|
|
26
30
|
private assertCanonicalActor;
|
|
27
31
|
admitChatGeneration(actor: CanonicalHistoryActor, input: ChatGenerationAdmissionEnvelope): Promise<AdmitChatGenerationResult>;
|
|
28
32
|
finalizeChatGeneration(actor: CanonicalHistoryActor, input: {
|
|
@@ -20,6 +20,8 @@ import { SYNCED_TABLE_MAP, ALLOWED_SYNC_TABLES } from "../db/schema.js";
|
|
|
20
20
|
import { emitWebhookSystemHook } from "~~/server/utils/webhooks/runtime";
|
|
21
21
|
import { incomingRevisionWins } from "~~/shared/sync/revision";
|
|
22
22
|
import { sanitizePayloadForSync } from "~~/shared/sync/sanitize";
|
|
23
|
+
import { readRequestUsage } from "~~/shared/chat/compaction";
|
|
24
|
+
import { validateCanonicalChatQuery, encodeCanonicalChatSeek, parseCanonicalChatSeek } from "~~/shared/chat/history-reader";
|
|
23
25
|
import { computePullRetention } from "./history-gc-policy.js";
|
|
24
26
|
const DEFAULT_PULL_LIMIT = 100;
|
|
25
27
|
const MAX_PULL_LIMIT = 1e3;
|
|
@@ -430,12 +432,58 @@ export class SqliteSyncGatewayAdapter {
|
|
|
430
432
|
snapshotBootstrap: "snapshot-v1",
|
|
431
433
|
historyRetention: "snapshot-v1",
|
|
432
434
|
backgroundGenerationHistory: "v1",
|
|
435
|
+
canonicalChatHistory: "v1",
|
|
433
436
|
workspaceItems: "v1"
|
|
434
437
|
};
|
|
435
438
|
}
|
|
436
439
|
get db() {
|
|
437
440
|
return getSqliteDb();
|
|
438
441
|
}
|
|
442
|
+
async readChatHistory(actor, query, signal) {
|
|
443
|
+
validateCanonicalChatQuery(query);
|
|
444
|
+
signal?.throwIfAborted();
|
|
445
|
+
if (isD1Driver()) return { status: "scope_incomplete" };
|
|
446
|
+
const raw = getRawDb();
|
|
447
|
+
const result = raw.transaction(() => {
|
|
448
|
+
const membership = raw.prepare(`SELECT m.role FROM workspace_members m JOIN workspaces w ON w.id = m.workspace_id
|
|
449
|
+
WHERE m.workspace_id = ? AND m.user_id = ? AND COALESCE(w.deleted, 0) = 0`).get(actor.workspaceId, actor.userId);
|
|
450
|
+
if (!membership || !["owner", "editor", "viewer"].includes(membership.role)) throw new Error("Forbidden canonical history actor");
|
|
451
|
+
const counter = raw.prepare("SELECT value FROM server_version_counter WHERE workspace_id = ?").get(actor.workspaceId);
|
|
452
|
+
const revision = String(counter?.value ?? 0);
|
|
453
|
+
const parse = (row) => {
|
|
454
|
+
const payload = JSON.parse(row.data_json);
|
|
455
|
+
if (payload.id !== row.id) throw new Error("Canonical history identity mismatch");
|
|
456
|
+
return { ...payload, id: row.id, clock: row.clock, deleted: row.deleted === 1 };
|
|
457
|
+
};
|
|
458
|
+
if (query.kind === "thread") {
|
|
459
|
+
const threadRevision = raw.prepare("SELECT value FROM chat_history_revisions WHERE workspace_id = ? AND thread_id = ?").get(actor.workspaceId, query.thread_id);
|
|
460
|
+
const row = raw.prepare("SELECT id, data_json, clock, deleted FROM s_threads WHERE workspace_id = ? AND id = ?").get(actor.workspaceId, query.thread_id);
|
|
461
|
+
return { status: "ok", revision: String(threadRevision?.value ?? 0), thread: row ? parse(row) : void 0 };
|
|
462
|
+
}
|
|
463
|
+
if (query.kind === "messages") {
|
|
464
|
+
if (!query.message_ids.length) return { status: "ok", revision, messages: [] };
|
|
465
|
+
const rows2 = raw.prepare(`SELECT id, data_json, clock, deleted FROM s_messages WHERE workspace_id = ? AND id IN (${query.message_ids.map(() => "?").join(",")})`).all(actor.workspaceId, ...query.message_ids);
|
|
466
|
+
return { status: "ok", revision, messages: rows2.map(parse) };
|
|
467
|
+
}
|
|
468
|
+
let after;
|
|
469
|
+
const seek = parseCanonicalChatSeek(query.cursor, query.thread_id);
|
|
470
|
+
if (seek) after = seek.key;
|
|
471
|
+
else if (query.cursor) {
|
|
472
|
+
const value = JSON.parse(query.cursor);
|
|
473
|
+
if (!Array.isArray(value) || value.length !== 4 || value[0] !== query.thread_id || !Number.isSafeInteger(value[1]) || typeof value[2] !== "string" || typeof value[3] !== "string") throw new Error("Invalid canonical history cursor");
|
|
474
|
+
after = [value[1], value[2], value[3]];
|
|
475
|
+
}
|
|
476
|
+
const rows = raw.prepare(`SELECT id, data_json, clock, deleted FROM s_messages
|
|
477
|
+
WHERE workspace_id = ? AND json_extract(data_json, '$.thread_id') = ?
|
|
478
|
+
${after ? `AND (json_extract(data_json, '$.index'), COALESCE(json_extract(data_json, '$.order_key'), ''), id) ${seek?.backward ? "<" : ">"} (?, ?, ?)` : ""}
|
|
479
|
+
ORDER BY json_extract(data_json, '$.index') ${seek?.backward ? "DESC" : "ASC"}, COALESCE(json_extract(data_json, '$.order_key'), '') ${seek?.backward ? "DESC" : "ASC"}, id ${seek?.backward ? "DESC" : "ASC"} LIMIT ?`).all(actor.workspaceId, query.thread_id, ...after ?? [], query.limit);
|
|
480
|
+
const messages = rows.map(parse);
|
|
481
|
+
const last = messages.at(-1);
|
|
482
|
+
return { status: "ok", revision, messages, next_cursor: messages.length === query.limit && last ? seek ? encodeCanonicalChatSeek({ ...seek, key: [Number(last.index), String(last.order_key ?? ""), last.id] }) : JSON.stringify([query.thread_id, last.index, last.order_key ?? "", last.id]) : void 0 };
|
|
483
|
+
})();
|
|
484
|
+
signal?.throwIfAborted();
|
|
485
|
+
return result;
|
|
486
|
+
}
|
|
439
487
|
assertCanonicalActor(actor) {
|
|
440
488
|
if (isD1Driver()) {
|
|
441
489
|
throw new Error("SQLite D1 does not support background generation history v1");
|
|
@@ -641,6 +689,7 @@ export class SqliteSyncGatewayAdapter {
|
|
|
641
689
|
const hlc = `${Date.now().toString(36).padStart(9, "0")}:000:background`;
|
|
642
690
|
const opId = backgroundHistoryOpId(admission, "finalize");
|
|
643
691
|
const terminalState = input.snapshot.status === "complete" ? "complete" : input.snapshot.status === "aborted" ? "aborted" : "failed";
|
|
692
|
+
const usage = readRequestUsage(input.snapshot.usage);
|
|
644
693
|
const payload = {
|
|
645
694
|
...current,
|
|
646
695
|
pending: false,
|
|
@@ -651,6 +700,7 @@ export class SqliteSyncGatewayAdapter {
|
|
|
651
700
|
op_id: opId,
|
|
652
701
|
data: {
|
|
653
702
|
...data,
|
|
703
|
+
...usage ? { usage } : {},
|
|
654
704
|
content: input.snapshot.content,
|
|
655
705
|
reasoning_text: input.snapshot.reasoning || null,
|
|
656
706
|
tool_calls: input.snapshot.toolCalls ?? null,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "or3-provider-sqlite",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/Saluana/or3-provider-sqlite.git"
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
"lint": "eslint src --max-warnings 0",
|
|
39
39
|
"type-check": "tsc --noEmit",
|
|
40
40
|
"type-check:standalone": "tsc -p tsconfig.release.json --noEmit",
|
|
41
|
-
"test": "vitest run"
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:host-integration": "vitest run --config vitest.host-integration.config.ts"
|
|
42
43
|
},
|
|
43
44
|
"publishConfig": {
|
|
44
45
|
"access": "public"
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"h3": "^1.15.4",
|
|
66
67
|
"pathe": "^2.0.3",
|
|
67
68
|
"typescript": "^5.9.3",
|
|
68
|
-
"vitest": "^4.0.18"
|
|
69
|
+
"vitest": "^4.0.18",
|
|
70
|
+
"zod": "4.6.5"
|
|
69
71
|
}
|
|
70
72
|
}
|