@sqlite-sync/ai 0.4.6

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 sqlite-sync contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,55 @@
1
+ # @sqlite-sync/ai
2
+
3
+ AI agent tools for [@sqlite-sync](https://github.com/krolebord-dev/sqlite-sync) databases. Gives an AI SDK agent safe, read-only access to a synced SQLite database.
4
+
5
+ ## What's included
6
+
7
+ - `createSchemaDoc` — generates a markdown schema doc by introspecting the synced database (structure from `PRAGMA table_info`, semantics from app-provided context).
8
+ - `createAiDbAccess` — server-side access object living next to the storage; its methods double as an RPC contract for cross-Durable-Object setups.
9
+ - `createDbTools` — AI SDK v6 `ToolSet` (currently a `getDbSchema` tool) backed by an `AiDbAccess` or a stub proxying to one.
10
+
11
+ ## Usage (Cloudflare Durable Object)
12
+
13
+ ```ts
14
+ import { createAiDbAccess } from "@sqlite-sync/ai";
15
+ import { createKyselyExecutor, durableObjectAdapter } from "@sqlite-sync/cloudflare";
16
+
17
+ // In the DO that owns the synced database:
18
+ async onStart() {
19
+ await durableObjectAdapter.createCrdtStorage({ syncDbSchema, storage: this.ctx.storage, /* ... */ });
20
+ this.aiDbAccess = createAiDbAccess({
21
+ executor: createKyselyExecutor(this.ctx.storage),
22
+ syncDbSchema,
23
+ context: {
24
+ overview: "# My app's database\n\nDeletes are soft-deletes; the views below already hide them.",
25
+ tables: {
26
+ todos: {
27
+ description: "The user's todos.",
28
+ columns: { completed: "1 when done." },
29
+ },
30
+ },
31
+ },
32
+ });
33
+ }
34
+
35
+ // RPC method for agents running elsewhere:
36
+ getDbSchemaDoc() {
37
+ return this.aiDbAccess.getSchemaDoc();
38
+ }
39
+ ```
40
+
41
+ ```ts
42
+ import { createDbTools } from "@sqlite-sync/ai";
43
+
44
+ // In the agent:
45
+ getTools() {
46
+ return createDbTools({
47
+ access: async () => {
48
+ const stub = await this.getUserDbStub();
49
+ return { getSchemaDoc: () => stub.getDbSchemaDoc() };
50
+ },
51
+ });
52
+ }
53
+ ```
54
+
55
+ `context.tables` keys are CRDT view names (`crdtTableName`). The doc skips the internal `tombstone` column and introspects base tables (views lose `NOT NULL` fidelity), presenting them under their view names.
@@ -0,0 +1,78 @@
1
+ import { SyncDbSchema } from '@sqlite-sync/core';
2
+ import { ToolSet } from 'ai';
3
+
4
+ /**
5
+ * App-provided semantics merged into the generated schema doc.
6
+ * Keys of `tables` are CRDT view names (`crdtTableName`), not base table names.
7
+ */
8
+ type SchemaDocContext = {
9
+ overview?: string;
10
+ tables?: Record<string, {
11
+ description?: string;
12
+ columns?: Record<string, string>;
13
+ }>;
14
+ };
15
+ /**
16
+ * Generates a markdown schema doc for an AI agent by introspecting the synced database.
17
+ * Structure comes from `PRAGMA table_info` on the base tables (introspecting the views would
18
+ * lose NOT NULL fidelity), presented under the view names the agent queries; semantics come
19
+ * from the consumer-provided context. The internal `tombstone` column is omitted.
20
+ */
21
+ declare function createSchemaDoc(opts: {
22
+ execute: (sql: string) => Record<string, unknown>[];
23
+ syncDbSchema: SyncDbSchema;
24
+ context?: SchemaDocContext;
25
+ }): string;
26
+
27
+ type AiDbExecuteParams = {
28
+ sql: string;
29
+ parameters: readonly unknown[];
30
+ };
31
+ /**
32
+ * Minimal executor contract for AI database access. Runtime-specific — inject the one that
33
+ * matches where the storage lives; @sqlite-sync/cloudflare's `createKyselyExecutor` satisfies it.
34
+ *
35
+ * `transaction` MUST roll back everything the callback executed when the callback throws —
36
+ * it backs the read-only enforcement of the query tooling.
37
+ */
38
+ type AiDbExecutor = {
39
+ execute<TResult = unknown>(query: AiDbExecuteParams): {
40
+ rows: TResult[];
41
+ };
42
+ transaction(callback: (tx: Pick<AiDbExecutor, "execute">) => void): void;
43
+ };
44
+ /**
45
+ * Read-only AI access to a synced database. Lives where the storage lives; its method names
46
+ * are the RPC contract, so a DO stub proxying to these methods exposes the same surface
47
+ * (promise-wrapped) and satisfies the tool layer's `DbToolsAccess`.
48
+ */
49
+ type AiDbAccess = {
50
+ getSchemaDoc(): string;
51
+ };
52
+ /**
53
+ * Create where the CRDT storage was created (e.g. a Durable Object's `onStart`, after
54
+ * migrations have run) — the schema doc is introspected once and cached.
55
+ */
56
+ declare function createAiDbAccess(opts: {
57
+ executor: AiDbExecutor;
58
+ syncDbSchema: SyncDbSchema;
59
+ context?: SchemaDocContext;
60
+ }): AiDbAccess;
61
+
62
+ type MaybePromise<T> = T | Promise<T>;
63
+ /**
64
+ * What the tools need from the database side. Satisfied directly by `AiDbAccess`, or by a
65
+ * Durable Object stub whose RPC methods delegate to one (RPC wraps returns in promises).
66
+ */
67
+ type DbToolsAccess = {
68
+ getSchemaDoc(): MaybePromise<string>;
69
+ };
70
+ /**
71
+ * AI SDK tools for a synced database. `access` is a factory because acquiring the database
72
+ * may itself be async per call (e.g. resolving a Durable Object stub from another DO).
73
+ */
74
+ declare function createDbTools(opts: {
75
+ access: () => MaybePromise<DbToolsAccess>;
76
+ }): ToolSet;
77
+
78
+ export { type AiDbAccess, type AiDbExecuteParams, type AiDbExecutor, type DbToolsAccess, type SchemaDocContext, createAiDbAccess, createDbTools, createSchemaDoc };
package/dist/index.js ADDED
@@ -0,0 +1,70 @@
1
+ // src/schema-doc.ts
2
+ function quoteId(name) {
3
+ return `"${name.replaceAll('"', '""')}"`;
4
+ }
5
+ function createSchemaDoc(opts) {
6
+ const sections = [];
7
+ const overview = opts.context?.overview?.trim();
8
+ if (overview) {
9
+ sections.push(overview);
10
+ }
11
+ for (const table of opts.syncDbSchema.tablesConfig) {
12
+ const tableContext = opts.context?.tables?.[table.crdtTableName];
13
+ const columns = opts.execute(`PRAGMA table_info(${quoteId(table.baseTableName)})`);
14
+ const lines = [`## ${table.crdtTableName}`];
15
+ if (tableContext?.description) {
16
+ lines.push("", tableContext.description.trim());
17
+ }
18
+ lines.push("", "Columns:");
19
+ for (const column of columns) {
20
+ if (column.name === "tombstone") continue;
21
+ const description = tableContext?.columns?.[column.name];
22
+ lines.push(
23
+ `- \`${column.name}\` ${column.type}${column.notnull ? " NOT NULL" : ""}${description ? ` \u2014 ${description}` : ""}`
24
+ );
25
+ }
26
+ sections.push(lines.join("\n"));
27
+ }
28
+ return sections.join("\n\n");
29
+ }
30
+
31
+ // src/db-access.ts
32
+ function createAiDbAccess(opts) {
33
+ let schemaDoc;
34
+ return {
35
+ getSchemaDoc() {
36
+ schemaDoc ??= createSchemaDoc({
37
+ execute: (sql) => opts.executor.execute({ sql, parameters: [] }).rows,
38
+ syncDbSchema: opts.syncDbSchema,
39
+ context: opts.context
40
+ });
41
+ return schemaDoc;
42
+ }
43
+ };
44
+ }
45
+
46
+ // src/tools.ts
47
+ import { jsonSchema, tool } from "ai";
48
+ var emptyInputSchema = jsonSchema({
49
+ type: "object",
50
+ properties: {},
51
+ additionalProperties: false
52
+ });
53
+ function createDbTools(opts) {
54
+ return {
55
+ getDbSchema: tool({
56
+ description: "Get the schema documentation for the synced SQLite database: tables, columns, types, and data conventions. Call this before reasoning about the data.",
57
+ inputSchema: emptyInputSchema,
58
+ execute: async () => {
59
+ const access = await opts.access();
60
+ return await access.getSchemaDoc();
61
+ }
62
+ })
63
+ };
64
+ }
65
+ export {
66
+ createAiDbAccess,
67
+ createDbTools,
68
+ createSchemaDoc
69
+ };
70
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema-doc.ts","../src/db-access.ts","../src/tools.ts"],"sourcesContent":["import type { SyncDbSchema } from \"@sqlite-sync/core\";\n\n/**\n * App-provided semantics merged into the generated schema doc.\n * Keys of `tables` are CRDT view names (`crdtTableName`), not base table names.\n */\nexport type SchemaDocContext = {\n overview?: string;\n tables?: Record<string, { description?: string; columns?: Record<string, string> }>;\n};\n\ntype TableInfoRow = {\n cid: number;\n name: string;\n type: string;\n notnull: number;\n};\n\nfunction quoteId(name: string) {\n return `\"${name.replaceAll('\"', '\"\"')}\"`;\n}\n\n/**\n * Generates a markdown schema doc for an AI agent by introspecting the synced database.\n * Structure comes from `PRAGMA table_info` on the base tables (introspecting the views would\n * lose NOT NULL fidelity), presented under the view names the agent queries; semantics come\n * from the consumer-provided context. The internal `tombstone` column is omitted.\n */\nexport function createSchemaDoc(opts: {\n execute: (sql: string) => Record<string, unknown>[];\n syncDbSchema: SyncDbSchema;\n context?: SchemaDocContext;\n}): string {\n const sections: string[] = [];\n\n const overview = opts.context?.overview?.trim();\n if (overview) {\n sections.push(overview);\n }\n\n for (const table of opts.syncDbSchema.tablesConfig) {\n const tableContext = opts.context?.tables?.[table.crdtTableName];\n const columns = opts.execute(`PRAGMA table_info(${quoteId(table.baseTableName)})`) as TableInfoRow[];\n\n const lines = [`## ${table.crdtTableName}`];\n if (tableContext?.description) {\n lines.push(\"\", tableContext.description.trim());\n }\n lines.push(\"\", \"Columns:\");\n for (const column of columns) {\n if (column.name === \"tombstone\") continue;\n const description = tableContext?.columns?.[column.name];\n lines.push(\n `- \\`${column.name}\\` ${column.type}${column.notnull ? \" NOT NULL\" : \"\"}${description ? ` — ${description}` : \"\"}`,\n );\n }\n sections.push(lines.join(\"\\n\"));\n }\n\n return sections.join(\"\\n\\n\");\n}\n","import type { SyncDbSchema } from \"@sqlite-sync/core\";\nimport { createSchemaDoc, type SchemaDocContext } from \"./schema-doc\";\n\nexport type AiDbExecuteParams = {\n sql: string;\n parameters: readonly unknown[];\n};\n\n/**\n * Minimal executor contract for AI database access. Runtime-specific — inject the one that\n * matches where the storage lives; @sqlite-sync/cloudflare's `createKyselyExecutor` satisfies it.\n *\n * `transaction` MUST roll back everything the callback executed when the callback throws —\n * it backs the read-only enforcement of the query tooling.\n */\nexport type AiDbExecutor = {\n execute<TResult = unknown>(query: AiDbExecuteParams): { rows: TResult[] };\n transaction(callback: (tx: Pick<AiDbExecutor, \"execute\">) => void): void;\n};\n\n/**\n * Read-only AI access to a synced database. Lives where the storage lives; its method names\n * are the RPC contract, so a DO stub proxying to these methods exposes the same surface\n * (promise-wrapped) and satisfies the tool layer's `DbToolsAccess`.\n */\nexport type AiDbAccess = {\n getSchemaDoc(): string;\n};\n\n/**\n * Create where the CRDT storage was created (e.g. a Durable Object's `onStart`, after\n * migrations have run) — the schema doc is introspected once and cached.\n */\nexport function createAiDbAccess(opts: {\n executor: AiDbExecutor;\n syncDbSchema: SyncDbSchema;\n context?: SchemaDocContext;\n}): AiDbAccess {\n let schemaDoc: string | undefined;\n\n return {\n getSchemaDoc() {\n schemaDoc ??= createSchemaDoc({\n execute: (sql) => opts.executor.execute<Record<string, unknown>>({ sql, parameters: [] }).rows,\n syncDbSchema: opts.syncDbSchema,\n context: opts.context,\n });\n return schemaDoc;\n },\n };\n}\n","import { jsonSchema, type ToolSet, tool } from \"ai\";\n\ntype MaybePromise<T> = T | Promise<T>;\n\n/**\n * What the tools need from the database side. Satisfied directly by `AiDbAccess`, or by a\n * Durable Object stub whose RPC methods delegate to one (RPC wraps returns in promises).\n */\nexport type DbToolsAccess = {\n getSchemaDoc(): MaybePromise<string>;\n};\n\nconst emptyInputSchema = jsonSchema<Record<string, never>>({\n type: \"object\",\n properties: {},\n additionalProperties: false,\n});\n\n/**\n * AI SDK tools for a synced database. `access` is a factory because acquiring the database\n * may itself be async per call (e.g. resolving a Durable Object stub from another DO).\n */\nexport function createDbTools(opts: { access: () => MaybePromise<DbToolsAccess> }): ToolSet {\n return {\n getDbSchema: tool({\n description:\n \"Get the schema documentation for the synced SQLite database: tables, columns, types, and data conventions. Call this before reasoning about the data.\",\n inputSchema: emptyInputSchema,\n execute: async () => {\n const access = await opts.access();\n return await access.getSchemaDoc();\n },\n }),\n };\n}\n"],"mappings":";AAkBA,SAAS,QAAQ,MAAc;AAC7B,SAAO,IAAI,KAAK,WAAW,KAAK,IAAI,CAAC;AACvC;AAQO,SAAS,gBAAgB,MAIrB;AACT,QAAM,WAAqB,CAAC;AAE5B,QAAM,WAAW,KAAK,SAAS,UAAU,KAAK;AAC9C,MAAI,UAAU;AACZ,aAAS,KAAK,QAAQ;AAAA,EACxB;AAEA,aAAW,SAAS,KAAK,aAAa,cAAc;AAClD,UAAM,eAAe,KAAK,SAAS,SAAS,MAAM,aAAa;AAC/D,UAAM,UAAU,KAAK,QAAQ,qBAAqB,QAAQ,MAAM,aAAa,CAAC,GAAG;AAEjF,UAAM,QAAQ,CAAC,MAAM,MAAM,aAAa,EAAE;AAC1C,QAAI,cAAc,aAAa;AAC7B,YAAM,KAAK,IAAI,aAAa,YAAY,KAAK,CAAC;AAAA,IAChD;AACA,UAAM,KAAK,IAAI,UAAU;AACzB,eAAW,UAAU,SAAS;AAC5B,UAAI,OAAO,SAAS,YAAa;AACjC,YAAM,cAAc,cAAc,UAAU,OAAO,IAAI;AACvD,YAAM;AAAA,QACJ,OAAO,OAAO,IAAI,MAAM,OAAO,IAAI,GAAG,OAAO,UAAU,cAAc,EAAE,GAAG,cAAc,WAAM,WAAW,KAAK,EAAE;AAAA,MAClH;AAAA,IACF;AACA,aAAS,KAAK,MAAM,KAAK,IAAI,CAAC;AAAA,EAChC;AAEA,SAAO,SAAS,KAAK,MAAM;AAC7B;;;AC3BO,SAAS,iBAAiB,MAIlB;AACb,MAAI;AAEJ,SAAO;AAAA,IACL,eAAe;AACb,oBAAc,gBAAgB;AAAA,QAC5B,SAAS,CAAC,QAAQ,KAAK,SAAS,QAAiC,EAAE,KAAK,YAAY,CAAC,EAAE,CAAC,EAAE;AAAA,QAC1F,cAAc,KAAK;AAAA,QACnB,SAAS,KAAK;AAAA,MAChB,CAAC;AACD,aAAO;AAAA,IACT;AAAA,EACF;AACF;;;AClDA,SAAS,YAA0B,YAAY;AAY/C,IAAM,mBAAmB,WAAkC;AAAA,EACzD,MAAM;AAAA,EACN,YAAY,CAAC;AAAA,EACb,sBAAsB;AACxB,CAAC;AAMM,SAAS,cAAc,MAA8D;AAC1F,SAAO;AAAA,IACL,aAAa,KAAK;AAAA,MAChB,aACE;AAAA,MACF,aAAa;AAAA,MACb,SAAS,YAAY;AACnB,cAAM,SAAS,MAAM,KAAK,OAAO;AACjC,eAAO,MAAM,OAAO,aAAa;AAAA,MACnC;AAAA,IACF,CAAC;AAAA,EACH;AACF;","names":[]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@sqlite-sync/ai",
3
+ "version": "0.4.6",
4
+ "description": "AI agent tools for @sqlite-sync databases",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/krolebord-dev/sqlite-sync",
10
+ "directory": "packages/ai"
11
+ },
12
+ "keywords": [
13
+ "sqlite",
14
+ "sync",
15
+ "ai",
16
+ "agent",
17
+ "tools",
18
+ "offline-first",
19
+ "local-first"
20
+ ],
21
+ "main": "./dist/index.js",
22
+ "module": "./dist/index.js",
23
+ "types": "./dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "peerDependencies": {
34
+ "@sqlite-sync/core": "^0.4.6",
35
+ "ai": "^6.0.0"
36
+ },
37
+ "devDependencies": {
38
+ "ai": "^6.0.201",
39
+ "tsup": "^8.3.5",
40
+ "typescript": "~6.0.3",
41
+ "vitest": "^4.1.8",
42
+ "@sqlite-sync/core": "0.4.6"
43
+ },
44
+ "scripts": {
45
+ "build": "tsup",
46
+ "dev": "tsup --watch",
47
+ "test": "vitest run",
48
+ "typecheck": "tsgo --noEmit"
49
+ }
50
+ }