History

Move provider event SQL access into Effect repository

Cursor Agent committed 8 months ago

Commit bce0df01 parent d6e0cd82 files · +231 −62Browse files

Unified · Split

2 files
  1. apps/server/src/persistence/providerEventsRepo.ts +124 −0
  2. apps/server/src/persistenceService.ts +107 −62
apps/server/src/persistence/providerEventsRepo.ts+124−0
@@ -0,0 +1,124 @@
1import * as Effect from "effect/Effect";
2import * as SqlClient from "effect/unstable/sql/SqlClient";
3
4function toSafeInteger(value: unknown, fallback = 0): number {
5 if (typeof value === "number" && Number.isFinite(value)) {
6 return Math.trunc(value);
7 }
8 if (typeof value === "bigint") {
9 return Number(value);
10 }
11 return fallback;
12}
13
14export interface CompletedProviderItemRow {
15 item_id: string | null;
16 payload_json: string | null;
17}
18
19export const insertProviderEvent = (input: {
20 id: string;
21 sessionId: string;
22 provider: string;
23 kind: string;
24 method: string;
25 runtimeThreadId: string | null;
26 turnId: string | null;
27 itemId: string | null;
28 requestId: string | null;
29 requestKind: string | null;
30 textDelta: string | null;
31 message: string | null;
32 payloadJson: string | null;
33 createdAt: string;
34}): Effect.Effect<number, unknown, SqlClient.SqlClient> =>
35 Effect.gen(function*() {
36 const sql = yield* SqlClient.SqlClient;
37 const rawResult = (yield* sql
38 .unsafe<{ changes?: number | bigint }>(
39 `INSERT OR IGNORE INTO provider_events (
40 id,
41 session_id,
42 provider,
43 kind,
44 method,
45 thread_id,
46 turn_id,
47 item_id,
48 request_id,
49 request_kind,
50 text_delta,
51 message,
52 payload_json,
53 created_at
54 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
55 [
56 input.id,
57 input.sessionId,
58 input.provider,
59 input.kind,
60 input.method,
61 input.runtimeThreadId,
62 input.turnId,
63 input.itemId,
64 input.requestId,
65 input.requestKind,
66 input.textDelta,
67 input.message,
68 input.payloadJson,
69 input.createdAt,
70 ],
71 )
72 .raw) as { changes?: number | bigint };
73 return toSafeInteger(rawResult.changes, 0);
74 });
75
76export const listCompletedItemEventsBySessionTurn = (input: {
77 sessionId: string;
78 turnId: string;
79}): Effect.Effect<CompletedProviderItemRow[], unknown, SqlClient.SqlClient> =>
80 Effect.gen(function*() {
81 const sql = yield* SqlClient.SqlClient;
82 return (yield* sql
83 .unsafe<CompletedProviderItemRow>(
84 `SELECT item_id, payload_json
85 FROM provider_events
86 WHERE session_id = ? AND turn_id = ? AND method = 'item/completed'
87 ORDER BY created_at DESC;`,
88 [input.sessionId, input.turnId],
89 )
90 .unprepared) as CompletedProviderItemRow[];
91 });
92
93export const listCompletedItemEventsByThreadTurn = (input: {
94 runtimeThreadId: string;
95 turnId: string;
96}): Effect.Effect<CompletedProviderItemRow[], unknown, SqlClient.SqlClient> =>
97 Effect.gen(function*() {
98 const sql = yield* SqlClient.SqlClient;
99 return (yield* sql
100 .unsafe<CompletedProviderItemRow>(
101 `SELECT item_id, payload_json
102 FROM provider_events
103 WHERE thread_id = ? AND turn_id = ? AND method = 'item/completed'
104 ORDER BY created_at DESC;`,
105 [input.runtimeThreadId, input.turnId],
106 )
107 .unprepared) as CompletedProviderItemRow[];
108 });
109
110export const listCompletedItemEventsByTurn = (
111 turnId: string,
112): Effect.Effect<CompletedProviderItemRow[], unknown, SqlClient.SqlClient> =>
113 Effect.gen(function*() {
114 const sql = yield* SqlClient.SqlClient;
115 return (yield* sql
116 .unsafe<CompletedProviderItemRow>(
117 `SELECT item_id, payload_json
118 FROM provider_events
119 WHERE turn_id = ? AND method = 'item/completed'
120 ORDER BY created_at DESC;`,
121 [turnId],
122 )
123 .unprepared) as CompletedProviderItemRow[];
124 });
apps/server/src/persistenceService.ts+107−62
@@ -73,6 +73,13 @@ import { · 72 unmodified lines
73 upsertDocument as upsertDocumentEffect,73 upsertDocument as upsertDocumentEffect,
74 type DocumentRow,74 type DocumentRow,
75} from "./persistence/documentsRepo";75} from "./persistence/documentsRepo";
76import {
77 insertProviderEvent as insertProviderEventEffect,
78 listCompletedItemEventsBySessionTurn as listCompletedItemEventsBySessionTurnEffect,
79 listCompletedItemEventsByThreadTurn as listCompletedItemEventsByThreadTurnEffect,
80 listCompletedItemEventsByTurn as listCompletedItemEventsByTurnEffect,
81 type CompletedProviderItemRow,
82} from "./persistence/providerEventsRepo";
76import {83import {
77 appendStateEvent as appendStateEventEffect,84 appendStateEvent as appendStateEventEffect,
78 listStateEventsAfterSeq as listStateEventsAfterSeqEffect,85 listStateEventsAfterSeq as listStateEventsAfterSeqEffect,
@@ -1570,38 +1577,55 @@ export class PersistenceService extends EventEmitter<PersistenceServiceEvents> { · 1491 unmodified lines
1570 sessionId?: string;1577 sessionId?: string;
1571 runtimeThreadId?: string | null;1578 runtimeThreadId?: string | null;
1572 }): string | undefined {1579 }): string | undefined {
1573 const queries: Array<{ sql: string; params: unknown[] }> = [];1580 const rowGroups: CompletedProviderItemRow[][] = [];
1574 if (input.sessionId) {1581 if (input.sessionId) {
1575 queries.push({1582 rowGroups.push(
1576 sql: `SELECT item_id, payload_json1583 this.runWithEffectSql(
1577 FROM provider_events1584 listCompletedItemEventsBySessionTurnEffect({
1578 WHERE session_id = ? AND turn_id = ? AND method = 'item/completed'1585 sessionId: input.sessionId,
1579 ORDER BY created_at DESC;`,1586 turnId: input.turnId,
1580 params: [input.sessionId, input.turnId],1587 }),
1581 });1588 () =>
1589 this.db.prepare(
1590 `SELECT item_id, payload_json
1591 FROM provider_events
1592 WHERE session_id = ? AND turn_id = ? AND method = 'item/completed'
1593 ORDER BY created_at DESC;`,
1594 ).all(input.sessionId, input.turnId) as CompletedProviderItemRow[],
1595 ),
1596 );
1582 }1597 }
1583 if (input.runtimeThreadId) {1598 if (input.runtimeThreadId) {
1584 queries.push({1599 rowGroups.push(
1585 sql: `SELECT item_id, payload_json1600 this.runWithEffectSql(
1586 FROM provider_events1601 listCompletedItemEventsByThreadTurnEffect({
1587 WHERE thread_id = ? AND turn_id = ? AND method = 'item/completed'1602 runtimeThreadId: input.runtimeThreadId,
1588 ORDER BY created_at DESC;`,1603 turnId: input.turnId,
1589 params: [input.runtimeThreadId, input.turnId],1604 }),
1590 });1605 () =>
1606 this.db.prepare(
1607 `SELECT item_id, payload_json
1608 FROM provider_events
1609 WHERE thread_id = ? AND turn_id = ? AND method = 'item/completed'
1610 ORDER BY created_at DESC;`,
1611 ).all(input.runtimeThreadId, input.turnId) as CompletedProviderItemRow[],
1612 ),
1613 );
1591 }1614 }
1592 queries.push({1615 rowGroups.push(
1593 sql: `SELECT item_id, payload_json1616 this.runWithEffectSql(
1594 FROM provider_events1617 listCompletedItemEventsByTurnEffect(input.turnId),
1595 WHERE turn_id = ? AND method = 'item/completed'1618 () =>
1596 ORDER BY created_at DESC;`,1619 this.db.prepare(
1597 params: [input.turnId],1620 `SELECT item_id, payload_json
1598 });1621 FROM provider_events
1622 WHERE turn_id = ? AND method = 'item/completed'
1623 ORDER BY created_at DESC;`,
1624 ).all(input.turnId) as CompletedProviderItemRow[],
1625 ),
1626 );
1599 1627
1600 for (const query of queries) {1628 for (const rows of rowGroups) {
1601 const rows = this.db.prepare(query.sql).all(...query.params) as Array<{
1602 item_id: string | null;
1603 payload_json: string | null;
1604 }>;
1605 for (const row of rows) {1629 for (const row of rows) {
1606 const payload = row.payload_json ? this.tryParseJson(row.payload_json) : null;1630 const payload = row.payload_json ? this.tryParseJson(row.payload_json) : null;
1607 const item = asObject(asObject(payload)?.item);1631 const item = asObject(asObject(payload)?.item);
@@ -1774,43 +1798,64 @@ export class PersistenceService extends EventEmitter<PersistenceServiceEvents> { · 166 unmodified lines
1774 private insertProviderEvent(event: ProviderEvent): ProviderEventInsertResult {1798 private insertProviderEvent(event: ProviderEvent): ProviderEventInsertResult {
1775 const runtimeThreadId = event.threadId ?? parseThreadIdFromEventPayload(event.payload);1799 const runtimeThreadId = event.threadId ?? parseThreadIdFromEventPayload(event.payload);
1776 const payloadJson = event.payload === undefined ? null : JSON.stringify(event.payload);1800 const payloadJson = event.payload === undefined ? null : JSON.stringify(event.payload);
1777 const result = this.db1801 const changes = this.runWithEffectSql(
1778 .prepare(1802 insertProviderEventEffect({
1779 `INSERT OR IGNORE INTO provider_events (1803 id: event.id,
1780 id,1804 sessionId: event.sessionId,
1781 session_id,1805 provider: event.provider,
1782 provider,1806 kind: event.kind,
1783 kind,1807 method: event.method,
1784 method,1808 runtimeThreadId: runtimeThreadId ?? null,
1785 thread_id,1809 turnId: event.turnId ?? null,
1786 turn_id,1810 itemId: event.itemId ?? null,
1787 item_id,1811 requestId: event.requestId ?? null,
1788 request_id,1812 requestKind: event.requestKind ?? null,
1789 request_kind,1813 textDelta: event.textDelta ?? null,
1790 text_delta,1814 message: event.message ?? null,
1791 message,
1792 payload_json,
1793 created_at
1794 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
1795 )
1796 .run(
1797 event.id,
1798 event.sessionId,
1799 event.provider,
1800 event.kind,
1801 event.method,
1802 runtimeThreadId,
1803 event.turnId ?? null,
1804 event.itemId ?? null,
1805 event.requestId ?? null,
1806 event.requestKind ?? null,
1807 event.textDelta ?? null,
1808 event.message ?? null,
1809 payloadJson,1815 payloadJson,
1810 event.createdAt,1816 createdAt: event.createdAt,
1811 ) as { changes?: number | bigint };1817 }),
1818 () => {
1819 const result = this.db
1820 .prepare(
1821 `INSERT OR IGNORE INTO provider_events (
1822 id,
1823 session_id,
1824 provider,
1825 kind,
1826 method,
1827 thread_id,
1828 turn_id,
1829 item_id,
1830 request_id,
1831 request_kind,
1832 text_delta,
1833 message,
1834 payload_json,
1835 created_at
1836 ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);`,
1837 )
1838 .run(
1839 event.id,
1840 event.sessionId,
1841 event.provider,
1842 event.kind,
1843 event.method,
1844 runtimeThreadId,
1845 event.turnId ?? null,
1846 event.itemId ?? null,
1847 event.requestId ?? null,
1848 event.requestKind ?? null,
1849 event.textDelta ?? null,
1850 event.message ?? null,
1851 payloadJson,
1852 event.createdAt,
1853 ) as { changes?: number | bigint };
1854 return toSafeInteger(result.changes, 0);
1855 },
1856 );
1812 return {1857 return {
1813 inserted: toSafeInteger(result.changes, 0) > 0,1858 inserted: changes > 0,
1814 runtimeThreadId: runtimeThreadId ?? null,1859 runtimeThreadId: runtimeThreadId ?? null,
1815 };1860 };
1816 }1861 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t