@persql/context 0.1.0 → 1.0.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/README.md +83 -36
- package/dist/{chunk-RMG66FDI.js → chunk-HRHBXOPL.js} +91 -113
- package/dist/chunk-HRHBXOPL.js.map +1 -0
- package/dist/core.cjs +93 -116
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +54 -48
- package/dist/core.d.ts +54 -48
- package/dist/core.js +7 -9
- package/dist/index.cjs +211 -178
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -40
- package/dist/index.d.ts +34 -40
- package/dist/index.js +124 -71
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-RMG66FDI.js.map +0 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["/**\n * @persql/context — shared, structured agent context on the PerSQL substrate.\n *\n * One store, readable and writable from every agent surface PerSQL speaks\n * (SDK, MCP, published endpoints, CLI). Reads and structured writes are plain\n * SQL over `@persql/sdk` — cheap, exact, lexical (FTS5). The raw-dump extract\n * path puts the LLM on the *write* side: file once, recall by keyword forever.\n */\n\nimport type { PerSQLDatabase } from \"@persql/sdk\";\nimport {\n buildInit,\n buildRemember,\n buildSupersede,\n buildForget,\n buildGet,\n buildRecall,\n buildRecent,\n buildByTag,\n buildStats,\n buildUpsertEntity,\n buildUpsertEdge,\n buildNeighborEntities,\n buildEdgesAmong,\n entityId,\n type RememberInput,\n type RecallOptions,\n type ListOptions,\n type MemoryRow,\n type EntityInput,\n type Edge,\n type Entity,\n type ExtractedContext,\n type SqlStatement,\n} from \"./core\";\n\nexport * from \"./core\";\n\n/** Bring-your-own-LLM extractor, or use the hosted PerSQL Context worker. */\nexport type Extractor = (\n raw: string,\n ctx: { hint?: string }\n) => Promise<ExtractedContext>;\n\nexport interface ContextStoreOptions {\n /** Default extractor for `rememberRaw` (model-agnostic; you supply it). */\n extract?: Extractor;\n /** Default provenance label stamped on writes (e.g. \"claude-code\"). */\n source?: string;\n}\n\nexport interface WriteResult {\n memoryIds: string[];\n entityIds: string[];\n edgeCount: number;\n}\n\nexport interface ContextStats {\n facts: number;\n factsTotal: number;\n entities: number;\n edges: number;\n}\n\nfunction uid(prefix: string): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } };\n const r =\n g.crypto?.randomUUID?.() ??\n `${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;\n return prefix + r;\n}\n\nexport class ContextStore {\n constructor(\n private readonly db: PerSQLDatabase,\n private readonly opts: ContextStoreOptions = {}\n ) {}\n\n /** Create the schema (idempotent). Run once before first use. */\n async init(): Promise<void> {\n await this.db.batch(buildInit());\n }\n\n // --- writes -------------------------------------------------------------\n\n /** Store one structured fact/episode/artifact. Returns its id. */\n async remember(input: RememberInput): Promise<string> {\n const id = input.id ?? uid(\"m_\");\n const now = Date.now();\n const stmts: SqlStatement[] = [\n buildRemember({ ...input, source: input.source ?? this.opts.source }, now, id),\n ];\n const sup = input.supersedes\n ? Array.isArray(input.supersedes)\n ? input.supersedes\n : [input.supersedes]\n : [];\n for (const old of sup) stmts.push(buildSupersede(old, id));\n await this.db.transaction(stmts);\n return id;\n }\n\n /**\n * Extract durable context from raw text and store it. The LLM runs on the\n * write path; pass `extract` here or to the constructor (or use the hosted\n * Context MCP, which runs extraction server-side and meters it).\n */\n async rememberRaw(\n raw: string,\n opts: { extract?: Extractor; hint?: string; source?: string } = {}\n ): Promise<WriteResult> {\n const extract = opts.extract ?? this.opts.extract;\n if (!extract) {\n throw new Error(\n \"ContextStore.rememberRaw needs an extractor: pass { extract } here or to the constructor, or use the hosted PerSQL Context MCP.\"\n );\n }\n const extracted = await extract(raw, { hint: opts.hint });\n return this.write(extracted, { source: opts.source });\n }\n\n /** Persist an already-extracted bundle of facts, entities, and edges. */\n async write(\n extracted: ExtractedContext,\n opts: { source?: string } = {}\n ): Promise<WriteResult> {\n const now = Date.now();\n const source = opts.source ?? this.opts.source;\n const stmts: SqlStatement[] = [];\n const memoryIds: string[] = [];\n const entityIds = new Set<string>();\n\n for (const f of extracted.facts ?? []) {\n const id = uid(\"m_\");\n memoryIds.push(id);\n stmts.push(buildRemember({ ...f, source }, now, id));\n }\n for (const e of extracted.entities ?? []) {\n stmts.push(buildUpsertEntity(e, now));\n entityIds.add(e.id ?? entityId(e.name));\n }\n for (const ed of extracted.edges ?? []) {\n // Ensure both endpoints exist before linking them.\n stmts.push(buildUpsertEntity({ name: ed.src }, now));\n stmts.push(buildUpsertEntity({ name: ed.dst }, now));\n stmts.push(buildUpsertEdge({ ...ed, source: ed.source ?? source }, now));\n entityIds.add(entityId(ed.src));\n entityIds.add(entityId(ed.dst));\n }\n\n if (stmts.length) await this.db.transaction(stmts);\n return {\n memoryIds,\n entityIds: [...entityIds],\n edgeCount: (extracted.edges ?? []).length,\n };\n }\n\n /** Mark older memory rows as replaced by `newId` (filtered out of recall). */\n async supersede(oldIds: string | string[], newId: string): Promise<void> {\n const ids = Array.isArray(oldIds) ? oldIds : [oldIds];\n await this.db.transaction(ids.map((o) => buildSupersede(o, newId)));\n }\n\n /** Hard-delete a memory row. */\n async forget(id: string): Promise<void> {\n const s = buildForget(id);\n await this.db.query(s.sql, s.params);\n }\n\n /** Add a relationship between two entities (created if absent). */\n async link(\n src: string,\n rel: string,\n dst: string,\n opts: { source?: string } = {}\n ): Promise<void> {\n const now = Date.now();\n await this.db.transaction([\n buildUpsertEntity({ name: src }, now),\n buildUpsertEntity({ name: dst }, now),\n buildUpsertEdge(\n { src, rel, dst, source: opts.source ?? this.opts.source },\n now\n ),\n ]);\n }\n\n // --- reads (no AI, pure lexical SQL) ------------------------------------\n\n /** Keyword recall, BM25-ranked, most relevant first. */\n async recall(query: string, opts: RecallOptions = {}): Promise<MemoryRow[]> {\n const resolved: RecallOptions = { ...opts };\n if (opts.withinDays != null && opts.sinceMs == null) {\n resolved.sinceMs = Date.now() - opts.withinDays * 86_400_000;\n }\n const s = buildRecall(query, resolved);\n if (!s) return [];\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data;\n }\n\n /** Most recent memory rows. */\n async recent(opts: ListOptions = {}): Promise<MemoryRow[]> {\n const s = buildRecent(opts);\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data;\n }\n\n /** Memory rows carrying a given tag. */\n async byTag(tag: string, opts: ListOptions = {}): Promise<MemoryRow[]> {\n const s = buildByTag(tag, opts);\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data;\n }\n\n /** Fetch one memory row by id, or null. */\n async get(id: string): Promise<MemoryRow | null> {\n const s = buildGet(id);\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data[0] ?? null;\n }\n\n /** The subgraph around an entity: reachable entities + the edges among them. */\n async neighbors(\n name: string,\n opts: { depth?: number; limit?: number } = {}\n ): Promise<{ entities: Entity[]; edges: Edge[] }> {\n const seed = name.startsWith(\"e_\") ? name : entityId(name);\n const depth = Math.min(Math.max(opts.depth ?? 1, 1), 4);\n const entStmt = buildNeighborEntities(seed, depth, opts.limit ?? 50);\n const { data: entities } = await this.db.query<Entity>(\n entStmt.sql,\n entStmt.params\n );\n const ids = [seed, ...entities.map((e) => e.id)];\n const edgeStmt = buildEdgesAmong(ids);\n const { data: edges } = await this.db.query<Edge>(\n edgeStmt.sql,\n edgeStmt.params\n );\n return { entities, edges };\n }\n\n /** Counts: current facts, total facts, entities, edges. */\n async stats(): Promise<ContextStats> {\n const s = buildStats();\n const { data } = await this.db.query<{\n facts: number;\n facts_total: number;\n entities: number;\n edges: number;\n }>(s.sql, s.params);\n const r = data[0];\n return {\n facts: r?.facts ?? 0,\n factsTotal: r?.facts_total ?? 0,\n entities: r?.entities ?? 0,\n edges: r?.edges ?? 0,\n };\n }\n}\n\n/** Wrap a PerSQL database handle as a shared context store. */\nexport function context(\n db: PerSQLDatabase,\n opts?: ContextStoreOptions\n): ContextStore {\n return new ContextStore(db, opts);\n}\n","/**\n * @persql/context/core — zero-runtime-dependency engine.\n *\n * Schema DDL, SQL builders, and the extraction/compaction contracts shared by\n * the `@persql/context` SDK (client-side, over `@persql/sdk`) and the hosted\n * PerSQL Context worker (server-side, over `@persql/ai`). Keeping both sides on\n * the same builders means client-recall and server-recall can never drift.\n *\n * Retrieval is lexical: FTS5 with the porter stemmer, BM25-ranked. No vectors.\n */\n\nexport const SCHEMA_VERSION = 1;\nexport const TABLE_PREFIX = \"ctx_\";\nconst T = TABLE_PREFIX;\n\nexport type MemoryKind = \"fact\" | \"episode\" | \"artifact\";\n\nexport interface MemoryRow {\n id: string;\n kind: MemoryKind;\n topic: string | null;\n body: string;\n tags: string | null;\n source: string | null;\n createdAt: number;\n supersededBy: string | null;\n}\n\nexport interface Entity {\n id: string;\n name: string;\n kind: string | null;\n body: string | null;\n createdAt: number;\n}\n\nexport interface Edge {\n id: string;\n src: string;\n rel: string;\n dst: string;\n source: string | null;\n createdAt: number;\n}\n\nexport interface RememberInput {\n body: string;\n topic?: string;\n kind?: MemoryKind;\n tags?: string[] | string;\n source?: string;\n supersedes?: string | string[];\n id?: string;\n}\n\nexport interface EntityInput {\n name: string;\n kind?: string;\n body?: string;\n id?: string;\n}\n\nexport interface EdgeInput {\n src: string;\n rel: string;\n dst: string;\n source?: string;\n}\n\nexport interface RecallOptions {\n limit?: number;\n kind?: MemoryKind;\n operator?: \"or\" | \"and\";\n mode?: \"terms\" | \"raw\";\n sinceMs?: number;\n withinDays?: number;\n includeSuperseded?: boolean;\n}\n\nexport interface ListOptions {\n limit?: number;\n kind?: MemoryKind;\n includeSuperseded?: boolean;\n}\n\n/** The shape an extractor (LLM or the hosted worker) returns from raw text. */\nexport interface ExtractedContext {\n facts?: Array<{ body: string; topic?: string; tags?: string[]; kind?: MemoryKind }>;\n entities?: EntityInput[];\n edges?: EdgeInput[];\n}\n\nexport interface SqlStatement {\n sql: string;\n params: unknown[];\n}\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport const SCHEMA_SQL: string[] = [\n `CREATE TABLE IF NOT EXISTS ${T}memory (\n id TEXT PRIMARY KEY,\n kind TEXT NOT NULL DEFAULT 'fact',\n topic TEXT,\n body TEXT NOT NULL,\n tags TEXT,\n source TEXT,\n created_at INTEGER NOT NULL,\n superseded_by TEXT\n )`,\n `CREATE INDEX IF NOT EXISTS ${T}memory_kind ON ${T}memory(kind)`,\n `CREATE INDEX IF NOT EXISTS ${T}memory_created ON ${T}memory(created_at)`,\n `CREATE INDEX IF NOT EXISTS ${T}memory_superseded ON ${T}memory(superseded_by)`,\n // External-content FTS index: body text is not duplicated, only indexed.\n // Porter stemmer so \"invoice\" recalls \"invoicing\" without embeddings.\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${T}memory_fts USING fts5(\n topic, body, tags,\n content='${T}memory', content_rowid='rowid',\n tokenize='porter unicode61'\n )`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_ai AFTER INSERT ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(rowid, topic, body, tags)\n VALUES (new.rowid, new.topic, new.body, new.tags);\n END`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_ad AFTER DELETE ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, topic, body, tags)\n VALUES ('delete', old.rowid, old.topic, old.body, old.tags);\n END`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_au AFTER UPDATE ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, topic, body, tags)\n VALUES ('delete', old.rowid, old.topic, old.body, old.tags);\n INSERT INTO ${T}memory_fts(rowid, topic, body, tags)\n VALUES (new.rowid, new.topic, new.body, new.tags);\n END`,\n `CREATE TABLE IF NOT EXISTS ${T}entity (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n kind TEXT,\n body TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS ${T}edge (\n id TEXT PRIMARY KEY,\n src TEXT NOT NULL,\n rel TEXT NOT NULL,\n dst TEXT NOT NULL,\n source TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS ${T}edge_src ON ${T}edge(src)`,\n `CREATE INDEX IF NOT EXISTS ${T}edge_dst ON ${T}edge(dst)`,\n];\n\nexport function buildInit(): SqlStatement[] {\n return SCHEMA_SQL.map((sql) => ({ sql, params: [] }));\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst FTS_OPERATORS = new Set([\"and\", \"or\", \"not\", \"near\"]);\n\nexport function normalizeTags(tags?: string[] | string | null): string | null {\n if (!tags) return null;\n const arr = Array.isArray(tags) ? tags : String(tags).split(/[,\\s]+/);\n const norm = Array.from(\n new Set(arr.map((t) => t.trim().toLowerCase()).filter(Boolean))\n );\n return norm.length ? norm.join(\" \") : null;\n}\n\n/**\n * Turn arbitrary user text into a safe FTS5 MATCH expression. `terms` mode\n * (default) extracts word tokens, drops bare boolean operators, and ORs the\n * rest as quoted terms — so a caller can paste \"invoice OR billing\" or just\n * \"invoice billing\" and neither crashes the parser nor injects FTS syntax.\n * `raw` mode passes a hand-written FTS expression through untouched.\n */\nexport function toFtsQuery(\n input: string,\n opts: { operator?: \"or\" | \"and\"; mode?: \"terms\" | \"raw\" } = {}\n): string | null {\n if (opts.mode === \"raw\") return input.trim() || null;\n const tokens = (input.match(/[\\p{L}\\p{N}_]+/gu) ?? []).filter(\n (t) => !FTS_OPERATORS.has(t.toLowerCase())\n );\n if (!tokens.length) return null;\n const joiner = opts.operator === \"and\" ? \" AND \" : \" OR \";\n return tokens.map((t) => `\"${t}\"`).join(joiner);\n}\n\nfunction slugId(prefix: string, s: string): string {\n const base = s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n return prefix + (base || \"x\");\n}\n\n/** Deterministic id for an entity, keyed on its name (case-insensitive). */\nexport function entityId(name: string): string {\n return slugId(\"e_\", name);\n}\n\nfunction resolveEntityRef(ref: string): string {\n return ref.startsWith(\"e_\") ? ref : entityId(ref);\n}\n\nfunction edgeId(srcId: string, rel: string, dstId: string): string {\n return slugId(\"x_\", `${srcId}|${rel}|${dstId}`);\n}\n\nconst MEMORY_COLS =\n \"id, kind, topic, body, tags, source, created_at AS createdAt, superseded_by AS supersededBy\";\n\n// ---------------------------------------------------------------------------\n// Memory builders\n// ---------------------------------------------------------------------------\n\nexport function buildRemember(\n input: RememberInput,\n now: number,\n id: string\n): SqlStatement {\n return {\n sql: `INSERT INTO ${T}memory (id, kind, topic, body, tags, source, created_at, superseded_by)\n VALUES (?, ?, ?, ?, ?, ?, ?, NULL)`,\n params: [\n id,\n input.kind ?? \"fact\",\n input.topic ?? null,\n input.body,\n normalizeTags(input.tags),\n input.source ?? null,\n now,\n ],\n };\n}\n\nexport function buildSupersede(oldId: string, newId: string): SqlStatement {\n return {\n sql: `UPDATE ${T}memory SET superseded_by = ? WHERE id = ? AND superseded_by IS NULL`,\n params: [newId, oldId],\n };\n}\n\nexport function buildForget(id: string): SqlStatement {\n return { sql: `DELETE FROM ${T}memory WHERE id = ?`, params: [id] };\n}\n\nexport function buildGet(id: string): SqlStatement {\n return {\n sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE id = ?`,\n params: [id],\n };\n}\n\nexport function buildRecall(\n query: string,\n opts: RecallOptions = {}\n): SqlStatement | null {\n const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });\n if (!match) return null;\n const where: string[] = [`${T}memory_fts MATCH ?`];\n const params: unknown[] = [match];\n if (!opts.includeSuperseded) where.push(\"m.superseded_by IS NULL\");\n if (opts.kind) {\n where.push(\"m.kind = ?\");\n params.push(opts.kind);\n }\n if (opts.sinceMs != null) {\n where.push(\"m.created_at >= ?\");\n params.push(opts.sinceMs);\n }\n params.push(opts.limit ?? 8);\n return {\n sql: `SELECT m.id, m.kind, m.topic, m.body, m.tags, m.source,\n m.created_at AS createdAt, m.superseded_by AS supersededBy\n FROM ${T}memory_fts\n JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid\n WHERE ${where.join(\" AND \")}\n ORDER BY ${T}memory_fts.rank\n LIMIT ?`,\n params,\n };\n}\n\nexport function buildRecent(opts: ListOptions = {}): SqlStatement {\n const where: string[] = [];\n const params: unknown[] = [];\n if (!opts.includeSuperseded) where.push(\"superseded_by IS NULL\");\n if (opts.kind) {\n where.push(\"kind = ?\");\n params.push(opts.kind);\n }\n params.push(opts.limit ?? 20);\n return {\n sql: `SELECT ${MEMORY_COLS} FROM ${T}memory\n ${where.length ? \"WHERE \" + where.join(\" AND \") : \"\"}\n ORDER BY created_at DESC\n LIMIT ?`,\n params,\n };\n}\n\nexport function buildByTag(tag: string, opts: ListOptions = {}): SqlStatement {\n const t = tag.trim().toLowerCase();\n const where: string[] = [\"(' ' || COALESCE(tags, '') || ' ') LIKE ?\"];\n const params: unknown[] = [`% ${t} %`];\n if (!opts.includeSuperseded) where.push(\"superseded_by IS NULL\");\n if (opts.kind) {\n where.push(\"kind = ?\");\n params.push(opts.kind);\n }\n params.push(opts.limit ?? 20);\n return {\n sql: `SELECT ${MEMORY_COLS} FROM ${T}memory\n WHERE ${where.join(\" AND \")}\n ORDER BY created_at DESC\n LIMIT ?`,\n params,\n };\n}\n\nexport function buildStats(): SqlStatement {\n return {\n sql: `SELECT\n (SELECT count(*) FROM ${T}memory WHERE superseded_by IS NULL) AS facts,\n (SELECT count(*) FROM ${T}memory) AS facts_total,\n (SELECT count(*) FROM ${T}entity) AS entities,\n (SELECT count(*) FROM ${T}edge) AS edges`,\n params: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Graph builders\n// ---------------------------------------------------------------------------\n\nexport function buildUpsertEntity(e: EntityInput, now: number): SqlStatement {\n const id = e.id ?? entityId(e.name);\n return {\n sql: `INSERT INTO ${T}entity (id, name, kind, body, created_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n name = excluded.name,\n kind = COALESCE(excluded.kind, ${T}entity.kind),\n body = COALESCE(excluded.body, ${T}entity.body)`,\n params: [id, e.name, e.kind ?? null, e.body ?? null, now],\n };\n}\n\nexport function buildUpsertEdge(e: EdgeInput, now: number): SqlStatement {\n const srcId = resolveEntityRef(e.src);\n const dstId = resolveEntityRef(e.dst);\n const id = edgeId(srcId, e.rel, dstId);\n return {\n sql: `INSERT INTO ${T}edge (id, src, rel, dst, source, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n source = COALESCE(excluded.source, ${T}edge.source)`,\n params: [id, srcId, e.rel, dstId, e.source ?? null, now],\n };\n}\n\n/** Entities reachable from a seed within `depth` hops (seed excluded). */\nexport function buildNeighborEntities(\n seedId: string,\n depth: number,\n limit: number\n): SqlStatement {\n return {\n sql: `WITH RECURSIVE reach(id, depth) AS (\n SELECT ?, 0\n UNION\n SELECT CASE WHEN e.src = reach.id THEN e.dst ELSE e.src END,\n reach.depth + 1\n FROM reach\n JOIN ${T}edge e ON (e.src = reach.id OR e.dst = reach.id)\n WHERE reach.depth < ?\n )\n SELECT en.id, en.name, en.kind, en.body,\n en.created_at AS createdAt, MIN(reach.depth) AS depth\n FROM reach\n JOIN ${T}entity en ON en.id = reach.id\n WHERE reach.depth > 0 AND en.id <> ?\n GROUP BY en.id\n ORDER BY depth, en.name\n LIMIT ?`,\n // The seed reappears at depth >0 via a round-trip on undirected edges;\n // exclude it explicitly so it isn't listed as its own neighbor.\n params: [seedId, depth, seedId, limit],\n };\n}\n\nexport function buildEdgesAmong(ids: string[]): SqlStatement {\n if (!ids.length) {\n return { sql: `SELECT id, src, rel, dst, source, created_at AS createdAt FROM ${T}edge WHERE 0`, params: [] };\n }\n const ph = ids.map(() => \"?\").join(\", \");\n return {\n sql: `SELECT id, src, rel, dst, source, created_at AS createdAt\n FROM ${T}edge\n WHERE src IN (${ph}) AND dst IN (${ph})`,\n params: [...ids, ...ids],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Extraction / compaction contracts (used by the hosted worker + documented\n// for bring-your-own-LLM callers; kept here so the prompt is versioned with\n// the schema it has to populate).\n// ---------------------------------------------------------------------------\n\nexport const EXTRACTION_SYSTEM_PROMPT = `You extract durable, shareable context from raw text for a team of AI agents that will read it later by keyword.\n\nReturn JSON: { \"facts\": [...], \"entities\": [...], \"edges\": [...] }.\n- facts: atomic, self-contained statements worth remembering across sessions — decisions, conventions, constraints, identifiers, preferences. Each: { \"body\": string, \"topic\"?: string, \"tags\"?: string[] }. Prefer specific and stable over transient chatter.\n- entities: named things — services, people, tables, repos, concepts. Each: { \"name\": string, \"kind\"?: string, \"body\"?: short description }.\n- edges: relationships between entities, referencing entity names. Each: { \"src\": name, \"rel\": string, \"dst\": name }.\n\nBe conservative: omit anything ephemeral or low-value. Use lowercase tags. If nothing is worth keeping, return empty arrays.`;\n\nexport function buildExtractionMessages(\n raw: string,\n opts: { hint?: string } = {}\n): Array<{ role: \"system\" | \"user\"; content: string }> {\n return [\n { role: \"system\", content: EXTRACTION_SYSTEM_PROMPT },\n {\n role: \"user\",\n content: (opts.hint ? `Context: ${opts.hint}\\n\\n` : \"\") + raw,\n },\n ];\n}\n\nexport const EXTRACTION_JSON_SCHEMA = {\n type: \"object\",\n properties: {\n facts: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n body: { type: \"string\" },\n topic: { type: \"string\" },\n tags: { type: \"array\", items: { type: \"string\" } },\n },\n required: [\"body\"],\n },\n },\n entities: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n kind: { type: \"string\" },\n body: { type: \"string\" },\n },\n required: [\"name\"],\n },\n },\n edges: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n src: { type: \"string\" },\n rel: { type: \"string\" },\n dst: { type: \"string\" },\n },\n required: [\"src\", \"rel\", \"dst\"],\n },\n },\n },\n} as const;\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/core.ts"],"sourcesContent":["/**\n * @persql/context — shared, structured agent context on the PerSQL substrate.\n *\n * Structured memories with name+description+type+body, UPSERT by name, and\n * an always-loadable index replace the old episode/fact model. One store per\n * project; every agent surface can read and write it over `@persql/sdk`.\n */\n\nimport type { PerSQLDatabase } from \"@persql/sdk\";\nimport {\n buildInit,\n buildRemember,\n buildForget,\n buildGet,\n buildIndex,\n buildRecall,\n buildStats,\n buildUpsertEntity,\n buildUpsertEdge,\n buildNeighborEntities,\n buildEdgesAmong,\n entityId,\n type MemoryType,\n type RememberInput,\n type RecallOptions,\n type ListOptions,\n type MemoryRow,\n type IndexRow,\n type EntityInput,\n type Edge,\n type Entity,\n type ExtractedContext,\n type SqlStatement,\n} from \"./core\";\n\nexport * from \"./core\";\n\nexport interface ContextStoreOptions {\n /** Default provenance label stamped on writes (e.g. \"claude-code\"). */\n source?: string;\n}\n\nexport interface WriteResult {\n names: string[];\n entityIds: string[];\n edgeCount: number;\n}\n\nexport interface ContextStats {\n memories: number;\n entities: number;\n edges: number;\n}\n\nfunction uid(prefix: string): string {\n const g = globalThis as { crypto?: { randomUUID?: () => string } };\n const r =\n g.crypto?.randomUUID?.() ??\n `${Date.now().toString(36)}-${Math.floor(Math.random() * 1e9).toString(36)}`;\n return prefix + r;\n}\n\nexport class ContextStore {\n constructor(\n private readonly db: PerSQLDatabase,\n private readonly opts: ContextStoreOptions = {}\n ) {}\n\n /** Create the schema (idempotent). Run once before first use. */\n async init(): Promise<void> {\n await this.db.batch(buildInit());\n }\n\n // --- writes -------------------------------------------------------------\n\n /** UPSERT a named memory. Same name → update; new name → insert. */\n async remember(input: RememberInput): Promise<void> {\n const id = uid(\"m_\");\n const now = Date.now();\n const s = buildRemember(\n { ...input, source: input.source ?? this.opts.source },\n now,\n id\n );\n await this.db.query(s.sql, s.params);\n }\n\n /** Persist an already-extracted bundle of memories, entities, and edges. */\n async write(\n extracted: ExtractedContext,\n opts: { source?: string } = {}\n ): Promise<WriteResult> {\n const now = Date.now();\n const source = opts.source ?? this.opts.source;\n const stmts: SqlStatement[] = [];\n const names: string[] = [];\n const entityIds = new Set<string>();\n\n for (const m of extracted.memories ?? []) {\n if (!m?.name || !m?.body) continue;\n const id = uid(\"m_\");\n names.push(m.name);\n stmts.push(buildRemember({ ...m, source }, now, id));\n }\n for (const e of extracted.entities ?? []) {\n stmts.push(buildUpsertEntity(e, now));\n entityIds.add(e.id ?? entityId(e.name));\n }\n for (const ed of extracted.edges ?? []) {\n stmts.push(buildUpsertEntity({ name: ed.src }, now));\n stmts.push(buildUpsertEntity({ name: ed.dst }, now));\n stmts.push(buildUpsertEdge({ ...ed, source: ed.source ?? source }, now));\n entityIds.add(entityId(ed.src));\n entityIds.add(entityId(ed.dst));\n }\n\n if (stmts.length) await this.db.transaction(stmts);\n return {\n names,\n entityIds: [...entityIds],\n edgeCount: (extracted.edges ?? []).length,\n };\n }\n\n /** Hard-delete a memory row by name. */\n async forget(name: string): Promise<void> {\n const s = buildForget(name);\n await this.db.query(s.sql, s.params);\n }\n\n /** Add a relationship between two entities (created if absent). */\n async link(\n src: string,\n rel: string,\n dst: string,\n opts: { source?: string } = {}\n ): Promise<void> {\n const now = Date.now();\n await this.db.transaction([\n buildUpsertEntity({ name: src }, now),\n buildUpsertEntity({ name: dst }, now),\n buildUpsertEdge(\n { src, rel, dst, source: opts.source ?? this.opts.source },\n now\n ),\n ]);\n }\n\n // --- reads (no AI, pure lexical SQL) ------------------------------------\n\n /** Always-loaded index: name+description+type, newest first. */\n async index(opts: ListOptions = {}): Promise<IndexRow[]> {\n const s = buildIndex(opts);\n const { data } = await this.db.query<IndexRow>(s.sql, s.params);\n return data;\n }\n\n /** Fetch one memory by name (with full body). */\n async get(name: string): Promise<MemoryRow | null> {\n const s = buildGet(name);\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data[0] ?? null;\n }\n\n /** Keyword recall, BM25-ranked, most relevant first. */\n async recall(query: string, opts: RecallOptions = {}): Promise<MemoryRow[]> {\n const s = buildRecall(query, opts);\n if (!s) return [];\n const { data } = await this.db.query<MemoryRow>(s.sql, s.params);\n return data;\n }\n\n /** The subgraph around an entity: reachable entities + the edges among them. */\n async neighbors(\n name: string,\n opts: { depth?: number; limit?: number } = {}\n ): Promise<{ entities: Entity[]; edges: Edge[] }> {\n const seed = name.startsWith(\"e_\") ? name : entityId(name);\n const depth = Math.min(Math.max(opts.depth ?? 1, 1), 4);\n const entStmt = buildNeighborEntities(seed, depth, opts.limit ?? 50);\n const { data: entities } = await this.db.query<Entity>(\n entStmt.sql,\n entStmt.params\n );\n const ids = [seed, ...entities.map((e) => e.id)];\n const edgeStmt = buildEdgesAmong(ids);\n const { data: edges } = await this.db.query<Edge>(\n edgeStmt.sql,\n edgeStmt.params\n );\n return { entities, edges };\n }\n\n /** Counts: memories, entities, edges. */\n async stats(): Promise<ContextStats> {\n const s = buildStats();\n const { data } = await this.db.query<{\n memories: number;\n entities: number;\n edges: number;\n }>(s.sql, s.params);\n const r = data[0];\n return {\n memories: r?.memories ?? 0,\n entities: r?.entities ?? 0,\n edges: r?.edges ?? 0,\n };\n }\n}\n\n/** Wrap a PerSQL database handle as a shared context store. */\nexport function context(\n db: PerSQLDatabase,\n opts?: ContextStoreOptions\n): ContextStore {\n return new ContextStore(db, opts);\n}\n\n// ---------------------------------------------------------------------------\n// Framework adapters\n// ---------------------------------------------------------------------------\n\n/** Tool definition shape for the OpenAI Agents SDK (`@openai/agents`). */\nexport interface MemoryTool {\n type: \"function\";\n name: string;\n description: string;\n parameters: Record<string, unknown>;\n invoke: (input: Record<string, unknown>) => Promise<unknown>;\n}\n\n/**\n * Returns three memory tools ready to spread into an OpenAI Agents SDK\n * `Agent({ tools })` call: `remember_memory`, `recall_memory`, `forget_memory`.\n *\n * The store must already be initialised (`await store.init()`) before the\n * first tool call.\n */\nexport function memoryTools(store: ContextStore): MemoryTool[] {\n return [\n {\n type: \"function\",\n name: \"remember_memory\",\n description:\n \"Save or update a named memory. Use for facts worth keeping across sessions: schema details, user preferences, decisions. Same name overwrites the previous entry.\",\n parameters: {\n type: \"object\",\n properties: {\n name: {\n type: \"string\",\n description: \"Short kebab-case key, e.g. 'billing-preference'\",\n },\n description: {\n type: \"string\",\n description: \"One-line summary shown in the memory index\",\n },\n type: {\n type: \"string\",\n enum: [\"user\", \"feedback\", \"project\", \"reference\"],\n description: \"Memory category\",\n },\n body: {\n type: \"string\",\n description: \"Full content of the memory\",\n },\n },\n required: [\"name\", \"description\", \"body\"],\n additionalProperties: false,\n },\n invoke: async (input) => {\n await store.remember({\n name: String(input.name),\n description: String(input.description),\n type: (input.type as MemoryType) ?? \"project\",\n body: String(input.body),\n });\n return { saved: input.name };\n },\n },\n {\n type: \"function\",\n name: \"recall_memory\",\n description:\n \"Search memories by keyword. Returns the most relevant entries (BM25-ranked). Use when the answer might be in memory before querying a live data source.\",\n parameters: {\n type: \"object\",\n properties: {\n query: {\n type: \"string\",\n description: \"Keywords to search for\",\n },\n limit: {\n type: \"number\",\n description: \"Max results (default 10)\",\n },\n },\n required: [\"query\"],\n additionalProperties: false,\n },\n invoke: async (input) => {\n const rows = await store.recall(String(input.query), {\n limit: typeof input.limit === \"number\" ? input.limit : 10,\n });\n return { memories: rows };\n },\n },\n {\n type: \"function\",\n name: \"forget_memory\",\n description: \"Delete a saved memory by name.\",\n parameters: {\n type: \"object\",\n properties: {\n name: { type: \"string\", description: \"Name of the memory to delete\" },\n },\n required: [\"name\"],\n additionalProperties: false,\n },\n invoke: async (input) => {\n await store.forget(String(input.name));\n return { deleted: input.name };\n },\n },\n ];\n}\n","/**\n * @persql/context/core — zero-runtime-dependency engine.\n *\n * Schema DDL, SQL builders, and the extraction contract shared by the\n * `@persql/context` SDK (client-side, over `@persql/sdk`) and the hosted\n * PerSQL Context worker (server-side, over `@persql/ai`). Structured memories\n * with a name-keyed UPSERT + always-loaded index replace the old episode model.\n *\n * Retrieval is lexical: FTS5 with the porter stemmer, BM25-ranked. No vectors.\n */\n\nexport const SCHEMA_VERSION = 2;\nexport const TABLE_PREFIX = \"ctx_\";\nconst T = TABLE_PREFIX;\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\";\n\nexport interface MemoryRow {\n id: string;\n name: string;\n description: string;\n type: MemoryType;\n body: string;\n source: string | null;\n createdAt: number;\n updatedAt: number;\n}\n\nexport interface IndexRow {\n id: string;\n name: string;\n description: string;\n type: MemoryType;\n updatedAt: number;\n}\n\nexport interface Entity {\n id: string;\n name: string;\n kind: string | null;\n body: string | null;\n createdAt: number;\n}\n\nexport interface Edge {\n id: string;\n src: string;\n rel: string;\n dst: string;\n source: string | null;\n createdAt: number;\n}\n\nexport interface RememberInput {\n name: string;\n description: string;\n type?: MemoryType;\n body: string;\n source?: string;\n}\n\nexport interface EntityInput {\n name: string;\n kind?: string;\n body?: string;\n id?: string;\n}\n\nexport interface EdgeInput {\n src: string;\n rel: string;\n dst: string;\n source?: string;\n}\n\nexport interface RecallOptions {\n limit?: number;\n operator?: \"or\" | \"and\";\n mode?: \"terms\" | \"raw\";\n}\n\nexport interface ListOptions {\n limit?: number;\n type?: MemoryType;\n}\n\n/** Structured memories extracted by an LLM from raw text. */\nexport interface ExtractedContext {\n memories?: Array<{\n name: string;\n description: string;\n type?: MemoryType;\n body: string;\n }>;\n entities?: EntityInput[];\n edges?: EdgeInput[];\n}\n\nexport interface SqlStatement {\n sql: string;\n params: unknown[];\n}\n\n// ---------------------------------------------------------------------------\n// Schema\n// ---------------------------------------------------------------------------\n\nexport const SCHEMA_SQL: string[] = [\n `CREATE TABLE IF NOT EXISTS ${T}memory (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n description TEXT NOT NULL,\n type TEXT NOT NULL DEFAULT 'project',\n body TEXT NOT NULL,\n source TEXT,\n created_at INTEGER NOT NULL,\n updated_at INTEGER NOT NULL\n )`,\n `CREATE UNIQUE INDEX IF NOT EXISTS ${T}memory_name ON ${T}memory(name)`,\n // External-content FTS on name+description+body. Porter stemmer so\n // \"invoice\" recalls \"invoicing\" without embeddings.\n `CREATE VIRTUAL TABLE IF NOT EXISTS ${T}memory_fts USING fts5(\n name, description, body,\n content='${T}memory', content_rowid='rowid',\n tokenize='porter unicode61'\n )`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_ai AFTER INSERT ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(rowid, name, description, body)\n VALUES (new.rowid, new.name, new.description, new.body);\n END`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_ad AFTER DELETE ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)\n VALUES ('delete', old.rowid, old.name, old.description, old.body);\n END`,\n `CREATE TRIGGER IF NOT EXISTS ${T}memory_au AFTER UPDATE ON ${T}memory BEGIN\n INSERT INTO ${T}memory_fts(${T}memory_fts, rowid, name, description, body)\n VALUES ('delete', old.rowid, old.name, old.description, old.body);\n INSERT INTO ${T}memory_fts(rowid, name, description, body)\n VALUES (new.rowid, new.name, new.description, new.body);\n END`,\n `CREATE TABLE IF NOT EXISTS ${T}entity (\n id TEXT PRIMARY KEY,\n name TEXT NOT NULL,\n kind TEXT,\n body TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE TABLE IF NOT EXISTS ${T}edge (\n id TEXT PRIMARY KEY,\n src TEXT NOT NULL,\n rel TEXT NOT NULL,\n dst TEXT NOT NULL,\n source TEXT,\n created_at INTEGER NOT NULL\n )`,\n `CREATE INDEX IF NOT EXISTS ${T}edge_src ON ${T}edge(src)`,\n `CREATE INDEX IF NOT EXISTS ${T}edge_dst ON ${T}edge(dst)`,\n];\n\nexport function buildInit(): SqlStatement[] {\n return SCHEMA_SQL.map((sql) => ({ sql, params: [] }));\n}\n\n// ---------------------------------------------------------------------------\n// Legacy migration helpers\n// ---------------------------------------------------------------------------\n\n/** Returns a non-zero count when the old (topic-based) schema is present. */\nexport function buildDetectLegacySchema(): SqlStatement {\n return {\n sql: `SELECT count(*) AS n FROM pragma_table_info('${T}memory') WHERE name='topic'`,\n params: [],\n };\n}\n\n/**\n * Drop all ctx_ tables + triggers so buildInit() can recreate them cleanly.\n * Run in order — FTS must go first (it references the content table).\n */\nexport function buildDropLegacy(): SqlStatement[] {\n return [\n { sql: `DROP TABLE IF EXISTS ${T}memory_fts`, params: [] },\n { sql: `DROP TABLE IF EXISTS ${T}memory`, params: [] },\n { sql: `DROP TABLE IF EXISTS ${T}entity`, params: [] },\n { sql: `DROP TABLE IF EXISTS ${T}edge`, params: [] },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nconst FTS_OPERATORS = new Set([\"and\", \"or\", \"not\", \"near\"]);\n\n/**\n * Turn arbitrary user text into a safe FTS5 MATCH expression. `terms` mode\n * (default) extracts word tokens, drops bare boolean operators, and ORs the\n * rest as quoted terms. `raw` mode passes a hand-written FTS expression through.\n */\nexport function toFtsQuery(\n input: string,\n opts: { operator?: \"or\" | \"and\"; mode?: \"terms\" | \"raw\" } = {}\n): string | null {\n if (opts.mode === \"raw\") return input.trim() || null;\n const tokens = (input.match(/[\\p{L}\\p{N}_]+/gu) ?? []).filter(\n (t) => !FTS_OPERATORS.has(t.toLowerCase())\n );\n if (!tokens.length) return null;\n const joiner = opts.operator === \"and\" ? \" AND \" : \" OR \";\n return tokens.map((t) => `\"${t}\"`).join(joiner);\n}\n\nfunction slugId(prefix: string, s: string): string {\n const base = s\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 96);\n return prefix + (base || \"x\");\n}\n\nexport function entityId(name: string): string {\n return slugId(\"e_\", name);\n}\n\nfunction resolveEntityRef(ref: string): string {\n return ref.startsWith(\"e_\") ? ref : entityId(ref);\n}\n\nfunction edgeId(srcId: string, rel: string, dstId: string): string {\n return slugId(\"x_\", `${srcId}|${rel}|${dstId}`);\n}\n\nconst MEMORY_COLS =\n \"id, name, description, type, body, source, created_at AS createdAt, updated_at AS updatedAt\";\n\n// ---------------------------------------------------------------------------\n// Memory builders\n// ---------------------------------------------------------------------------\n\n/** UPSERT by name — same name overwrites description/body/type/source. */\nexport function buildRemember(\n input: RememberInput,\n now: number,\n id: string\n): SqlStatement {\n return {\n sql: `INSERT INTO ${T}memory (id, name, description, type, body, source, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(name) DO UPDATE SET\n description = excluded.description,\n body = excluded.body,\n type = excluded.type,\n source = excluded.source,\n updated_at = excluded.updated_at`,\n params: [\n id,\n input.name,\n input.description,\n input.type ?? \"project\",\n input.body,\n input.source ?? null,\n now,\n now,\n ],\n };\n}\n\n/** Delete a memory by its name slug. */\nexport function buildForget(name: string): SqlStatement {\n return { sql: `DELETE FROM ${T}memory WHERE name = ?`, params: [name] };\n}\n\n/** Fetch one memory row by name, or null if absent. */\nexport function buildGet(name: string): SqlStatement {\n return {\n sql: `SELECT ${MEMORY_COLS} FROM ${T}memory WHERE name = ?`,\n params: [name],\n };\n}\n\n/** Always-loaded index: name+description+type, newest first. */\nexport function buildIndex(opts: ListOptions = {}): SqlStatement {\n const where: string[] = [];\n const params: unknown[] = [];\n if (opts.type) {\n where.push(\"type = ?\");\n params.push(opts.type);\n }\n params.push(opts.limit ?? 50);\n return {\n sql: `SELECT id, name, description, type, updated_at AS updatedAt FROM ${T}memory\n ${where.length ? \"WHERE \" + where.join(\" AND \") : \"\"}\n ORDER BY updated_at DESC LIMIT ?`,\n params,\n };\n}\n\n/** BM25 recall across name+description+body. */\nexport function buildRecall(\n query: string,\n opts: RecallOptions = {}\n): SqlStatement | null {\n const match = toFtsQuery(query, { operator: opts.operator, mode: opts.mode });\n if (!match) return null;\n return {\n sql: `SELECT m.id, m.name, m.description, m.type, m.body, m.source,\n m.created_at AS createdAt, m.updated_at AS updatedAt\n FROM ${T}memory_fts\n JOIN ${T}memory m ON m.rowid = ${T}memory_fts.rowid\n WHERE ${T}memory_fts MATCH ?\n ORDER BY ${T}memory_fts.rank\n LIMIT ?`,\n params: [match, opts.limit ?? 8],\n };\n}\n\nexport function buildStats(): SqlStatement {\n return {\n sql: `SELECT\n (SELECT count(*) FROM ${T}memory) AS memories,\n (SELECT count(*) FROM ${T}entity) AS entities,\n (SELECT count(*) FROM ${T}edge) AS edges`,\n params: [],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Graph builders (unchanged from v1)\n// ---------------------------------------------------------------------------\n\nexport function buildUpsertEntity(e: EntityInput, now: number): SqlStatement {\n const id = e.id ?? entityId(e.name);\n return {\n sql: `INSERT INTO ${T}entity (id, name, kind, body, created_at)\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n name = excluded.name,\n kind = COALESCE(excluded.kind, ${T}entity.kind),\n body = COALESCE(excluded.body, ${T}entity.body)`,\n params: [id, e.name, e.kind ?? null, e.body ?? null, now],\n };\n}\n\nexport function buildUpsertEdge(e: EdgeInput, now: number): SqlStatement {\n const srcId = resolveEntityRef(e.src);\n const dstId = resolveEntityRef(e.dst);\n const id = edgeId(srcId, e.rel, dstId);\n return {\n sql: `INSERT INTO ${T}edge (id, src, rel, dst, source, created_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n source = COALESCE(excluded.source, ${T}edge.source)`,\n params: [id, srcId, e.rel, dstId, e.source ?? null, now],\n };\n}\n\nexport function buildNeighborEntities(\n seedId: string,\n depth: number,\n limit: number\n): SqlStatement {\n return {\n sql: `WITH RECURSIVE reach(id, depth) AS (\n SELECT ?, 0\n UNION\n SELECT CASE WHEN e.src = reach.id THEN e.dst ELSE e.src END,\n reach.depth + 1\n FROM reach\n JOIN ${T}edge e ON (e.src = reach.id OR e.dst = reach.id)\n WHERE reach.depth < ?\n )\n SELECT en.id, en.name, en.kind, en.body,\n en.created_at AS createdAt, MIN(reach.depth) AS depth\n FROM reach\n JOIN ${T}entity en ON en.id = reach.id\n WHERE reach.depth > 0 AND en.id <> ?\n GROUP BY en.id\n ORDER BY depth, en.name\n LIMIT ?`,\n // The seed appears at depth >0 on undirected round-trips — exclude it.\n params: [seedId, depth, seedId, limit],\n };\n}\n\nexport function buildEdgesAmong(ids: string[]): SqlStatement {\n if (!ids.length) {\n return { sql: `SELECT id, src, rel, dst, source, created_at AS createdAt FROM ${T}edge WHERE 0`, params: [] };\n }\n const ph = ids.map(() => \"?\").join(\", \");\n return {\n sql: `SELECT id, src, rel, dst, source, created_at AS createdAt\n FROM ${T}edge\n WHERE src IN (${ph}) AND dst IN (${ph})`,\n params: [...ids, ...ids],\n };\n}\n\n// ---------------------------------------------------------------------------\n// Extraction contract (versioned here so client and server stay in sync)\n// ---------------------------------------------------------------------------\n\nexport const EXTRACTION_SYSTEM_PROMPT = `You extract durable, structured memories from a conversation or document for an AI agent that will recall them across sessions.\n\nReturn JSON: { \"memories\": [...], \"entities\": [...], \"edges\": [...] }.\n- memories: facts worth remembering across sessions — decisions, conventions, constraints, identifiers, preferences, schema details. Each:\n {\n \"name\": string (kebab-case slug, unique key — e.g. \"auth-provider-choice\", \"todos-table-schema\", \"user-prefers-metrics\"),\n \"description\": string (one-liner shown in the always-loaded index),\n \"type\": \"user\"|\"feedback\"|\"project\"|\"reference\",\n \"body\": string (full markdown content with all relevant detail)\n }\n Types: user=who they are/preferences; feedback=guidance for agents; project=ongoing work/decisions; reference=where to find things.\n- entities: named things — services, people, tables, repos. Each: { \"name\": string, \"kind\"?: string, \"body\"?: string }\n- edges: relationships between entities. Each: { \"src\": name, \"rel\": string, \"dst\": name }\n\nBe conservative. Use unique, meaningful names. If nothing durable is found, return empty arrays.`;\n\nexport function buildExtractionMessages(\n raw: string,\n opts: { hint?: string } = {}\n): Array<{ role: \"system\" | \"user\"; content: string }> {\n return [\n { role: \"system\", content: EXTRACTION_SYSTEM_PROMPT },\n {\n role: \"user\",\n content: (opts.hint ? `Context: ${opts.hint}\\n\\n` : \"\") + raw,\n },\n ];\n}\n\nexport const EXTRACTION_JSON_SCHEMA = {\n type: \"object\",\n properties: {\n memories: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n description: { type: \"string\" },\n type: { type: \"string\", enum: [\"user\", \"feedback\", \"project\", \"reference\"] },\n body: { type: \"string\" },\n },\n required: [\"name\", \"description\", \"body\"],\n },\n },\n entities: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n name: { type: \"string\" },\n kind: { type: \"string\" },\n body: { type: \"string\" },\n },\n required: [\"name\"],\n },\n },\n edges: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n src: { type: \"string\" },\n rel: { type: \"string\" },\n dst: { type: \"string\" },\n },\n required: [\"src\", \"rel\", \"dst\"],\n },\n },\n },\n} as const;\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import { PerSQLDatabase } from '@persql/sdk';
|
|
2
|
-
import { ExtractedContext,
|
|
3
|
-
export { EXTRACTION_JSON_SCHEMA, EXTRACTION_SYSTEM_PROMPT, EdgeInput, EntityInput,
|
|
2
|
+
import { RememberInput, ExtractedContext, ListOptions, IndexRow, MemoryRow, RecallOptions, Entity, Edge } from './core.cjs';
|
|
3
|
+
export { EXTRACTION_JSON_SCHEMA, EXTRACTION_SYSTEM_PROMPT, EdgeInput, EntityInput, MemoryType, SCHEMA_SQL, SCHEMA_VERSION, SqlStatement, TABLE_PREFIX, buildDetectLegacySchema, buildDropLegacy, buildEdgesAmong, buildExtractionMessages, buildForget, buildGet, buildIndex, buildInit, buildNeighborEntities, buildRecall, buildRemember, buildStats, buildUpsertEdge, buildUpsertEntity, entityId, toFtsQuery } from './core.cjs';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @persql/context — shared, structured agent context on the PerSQL substrate.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* path puts the LLM on the *write* side: file once, recall by keyword forever.
|
|
8
|
+
* Structured memories with name+description+type+body, UPSERT by name, and
|
|
9
|
+
* an always-loadable index replace the old episode/fact model. One store per
|
|
10
|
+
* project; every agent surface can read and write it over `@persql/sdk`.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
|
-
/** Bring-your-own-LLM extractor, or use the hosted PerSQL Context worker. */
|
|
15
|
-
type Extractor = (raw: string, ctx: {
|
|
16
|
-
hint?: string;
|
|
17
|
-
}) => Promise<ExtractedContext>;
|
|
18
13
|
interface ContextStoreOptions {
|
|
19
|
-
/** Default extractor for `rememberRaw` (model-agnostic; you supply it). */
|
|
20
|
-
extract?: Extractor;
|
|
21
14
|
/** Default provenance label stamped on writes (e.g. "claude-code"). */
|
|
22
15
|
source?: string;
|
|
23
16
|
}
|
|
24
17
|
interface WriteResult {
|
|
25
|
-
|
|
18
|
+
names: string[];
|
|
26
19
|
entityIds: string[];
|
|
27
20
|
edgeCount: number;
|
|
28
21
|
}
|
|
29
22
|
interface ContextStats {
|
|
30
|
-
|
|
31
|
-
factsTotal: number;
|
|
23
|
+
memories: number;
|
|
32
24
|
entities: number;
|
|
33
25
|
edges: number;
|
|
34
26
|
}
|
|
@@ -38,38 +30,24 @@ declare class ContextStore {
|
|
|
38
30
|
constructor(db: PerSQLDatabase, opts?: ContextStoreOptions);
|
|
39
31
|
/** Create the schema (idempotent). Run once before first use. */
|
|
40
32
|
init(): Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
remember(input: RememberInput): Promise<
|
|
43
|
-
/**
|
|
44
|
-
* Extract durable context from raw text and store it. The LLM runs on the
|
|
45
|
-
* write path; pass `extract` here or to the constructor (or use the hosted
|
|
46
|
-
* Context MCP, which runs extraction server-side and meters it).
|
|
47
|
-
*/
|
|
48
|
-
rememberRaw(raw: string, opts?: {
|
|
49
|
-
extract?: Extractor;
|
|
50
|
-
hint?: string;
|
|
51
|
-
source?: string;
|
|
52
|
-
}): Promise<WriteResult>;
|
|
53
|
-
/** Persist an already-extracted bundle of facts, entities, and edges. */
|
|
33
|
+
/** UPSERT a named memory. Same name → update; new name → insert. */
|
|
34
|
+
remember(input: RememberInput): Promise<void>;
|
|
35
|
+
/** Persist an already-extracted bundle of memories, entities, and edges. */
|
|
54
36
|
write(extracted: ExtractedContext, opts?: {
|
|
55
37
|
source?: string;
|
|
56
38
|
}): Promise<WriteResult>;
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/** Hard-delete a memory row. */
|
|
60
|
-
forget(id: string): Promise<void>;
|
|
39
|
+
/** Hard-delete a memory row by name. */
|
|
40
|
+
forget(name: string): Promise<void>;
|
|
61
41
|
/** Add a relationship between two entities (created if absent). */
|
|
62
42
|
link(src: string, rel: string, dst: string, opts?: {
|
|
63
43
|
source?: string;
|
|
64
44
|
}): Promise<void>;
|
|
45
|
+
/** Always-loaded index: name+description+type, newest first. */
|
|
46
|
+
index(opts?: ListOptions): Promise<IndexRow[]>;
|
|
47
|
+
/** Fetch one memory by name (with full body). */
|
|
48
|
+
get(name: string): Promise<MemoryRow | null>;
|
|
65
49
|
/** Keyword recall, BM25-ranked, most relevant first. */
|
|
66
50
|
recall(query: string, opts?: RecallOptions): Promise<MemoryRow[]>;
|
|
67
|
-
/** Most recent memory rows. */
|
|
68
|
-
recent(opts?: ListOptions): Promise<MemoryRow[]>;
|
|
69
|
-
/** Memory rows carrying a given tag. */
|
|
70
|
-
byTag(tag: string, opts?: ListOptions): Promise<MemoryRow[]>;
|
|
71
|
-
/** Fetch one memory row by id, or null. */
|
|
72
|
-
get(id: string): Promise<MemoryRow | null>;
|
|
73
51
|
/** The subgraph around an entity: reachable entities + the edges among them. */
|
|
74
52
|
neighbors(name: string, opts?: {
|
|
75
53
|
depth?: number;
|
|
@@ -78,10 +56,26 @@ declare class ContextStore {
|
|
|
78
56
|
entities: Entity[];
|
|
79
57
|
edges: Edge[];
|
|
80
58
|
}>;
|
|
81
|
-
/** Counts:
|
|
59
|
+
/** Counts: memories, entities, edges. */
|
|
82
60
|
stats(): Promise<ContextStats>;
|
|
83
61
|
}
|
|
84
62
|
/** Wrap a PerSQL database handle as a shared context store. */
|
|
85
63
|
declare function context(db: PerSQLDatabase, opts?: ContextStoreOptions): ContextStore;
|
|
64
|
+
/** Tool definition shape for the OpenAI Agents SDK (`@openai/agents`). */
|
|
65
|
+
interface MemoryTool {
|
|
66
|
+
type: "function";
|
|
67
|
+
name: string;
|
|
68
|
+
description: string;
|
|
69
|
+
parameters: Record<string, unknown>;
|
|
70
|
+
invoke: (input: Record<string, unknown>) => Promise<unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns three memory tools ready to spread into an OpenAI Agents SDK
|
|
74
|
+
* `Agent({ tools })` call: `remember_memory`, `recall_memory`, `forget_memory`.
|
|
75
|
+
*
|
|
76
|
+
* The store must already be initialised (`await store.init()`) before the
|
|
77
|
+
* first tool call.
|
|
78
|
+
*/
|
|
79
|
+
declare function memoryTools(store: ContextStore): MemoryTool[];
|
|
86
80
|
|
|
87
|
-
export { type ContextStats, ContextStore, type ContextStoreOptions, Edge, Entity, ExtractedContext,
|
|
81
|
+
export { type ContextStats, ContextStore, type ContextStoreOptions, Edge, Entity, ExtractedContext, IndexRow, ListOptions, MemoryRow, type MemoryTool, RecallOptions, RememberInput, type WriteResult, context, memoryTools };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,34 +1,26 @@
|
|
|
1
1
|
import { PerSQLDatabase } from '@persql/sdk';
|
|
2
|
-
import { ExtractedContext,
|
|
3
|
-
export { EXTRACTION_JSON_SCHEMA, EXTRACTION_SYSTEM_PROMPT, EdgeInput, EntityInput,
|
|
2
|
+
import { RememberInput, ExtractedContext, ListOptions, IndexRow, MemoryRow, RecallOptions, Entity, Edge } from './core.js';
|
|
3
|
+
export { EXTRACTION_JSON_SCHEMA, EXTRACTION_SYSTEM_PROMPT, EdgeInput, EntityInput, MemoryType, SCHEMA_SQL, SCHEMA_VERSION, SqlStatement, TABLE_PREFIX, buildDetectLegacySchema, buildDropLegacy, buildEdgesAmong, buildExtractionMessages, buildForget, buildGet, buildIndex, buildInit, buildNeighborEntities, buildRecall, buildRemember, buildStats, buildUpsertEdge, buildUpsertEntity, entityId, toFtsQuery } from './core.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @persql/context — shared, structured agent context on the PerSQL substrate.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* path puts the LLM on the *write* side: file once, recall by keyword forever.
|
|
8
|
+
* Structured memories with name+description+type+body, UPSERT by name, and
|
|
9
|
+
* an always-loadable index replace the old episode/fact model. One store per
|
|
10
|
+
* project; every agent surface can read and write it over `@persql/sdk`.
|
|
12
11
|
*/
|
|
13
12
|
|
|
14
|
-
/** Bring-your-own-LLM extractor, or use the hosted PerSQL Context worker. */
|
|
15
|
-
type Extractor = (raw: string, ctx: {
|
|
16
|
-
hint?: string;
|
|
17
|
-
}) => Promise<ExtractedContext>;
|
|
18
13
|
interface ContextStoreOptions {
|
|
19
|
-
/** Default extractor for `rememberRaw` (model-agnostic; you supply it). */
|
|
20
|
-
extract?: Extractor;
|
|
21
14
|
/** Default provenance label stamped on writes (e.g. "claude-code"). */
|
|
22
15
|
source?: string;
|
|
23
16
|
}
|
|
24
17
|
interface WriteResult {
|
|
25
|
-
|
|
18
|
+
names: string[];
|
|
26
19
|
entityIds: string[];
|
|
27
20
|
edgeCount: number;
|
|
28
21
|
}
|
|
29
22
|
interface ContextStats {
|
|
30
|
-
|
|
31
|
-
factsTotal: number;
|
|
23
|
+
memories: number;
|
|
32
24
|
entities: number;
|
|
33
25
|
edges: number;
|
|
34
26
|
}
|
|
@@ -38,38 +30,24 @@ declare class ContextStore {
|
|
|
38
30
|
constructor(db: PerSQLDatabase, opts?: ContextStoreOptions);
|
|
39
31
|
/** Create the schema (idempotent). Run once before first use. */
|
|
40
32
|
init(): Promise<void>;
|
|
41
|
-
/**
|
|
42
|
-
remember(input: RememberInput): Promise<
|
|
43
|
-
/**
|
|
44
|
-
* Extract durable context from raw text and store it. The LLM runs on the
|
|
45
|
-
* write path; pass `extract` here or to the constructor (or use the hosted
|
|
46
|
-
* Context MCP, which runs extraction server-side and meters it).
|
|
47
|
-
*/
|
|
48
|
-
rememberRaw(raw: string, opts?: {
|
|
49
|
-
extract?: Extractor;
|
|
50
|
-
hint?: string;
|
|
51
|
-
source?: string;
|
|
52
|
-
}): Promise<WriteResult>;
|
|
53
|
-
/** Persist an already-extracted bundle of facts, entities, and edges. */
|
|
33
|
+
/** UPSERT a named memory. Same name → update; new name → insert. */
|
|
34
|
+
remember(input: RememberInput): Promise<void>;
|
|
35
|
+
/** Persist an already-extracted bundle of memories, entities, and edges. */
|
|
54
36
|
write(extracted: ExtractedContext, opts?: {
|
|
55
37
|
source?: string;
|
|
56
38
|
}): Promise<WriteResult>;
|
|
57
|
-
/**
|
|
58
|
-
|
|
59
|
-
/** Hard-delete a memory row. */
|
|
60
|
-
forget(id: string): Promise<void>;
|
|
39
|
+
/** Hard-delete a memory row by name. */
|
|
40
|
+
forget(name: string): Promise<void>;
|
|
61
41
|
/** Add a relationship between two entities (created if absent). */
|
|
62
42
|
link(src: string, rel: string, dst: string, opts?: {
|
|
63
43
|
source?: string;
|
|
64
44
|
}): Promise<void>;
|
|
45
|
+
/** Always-loaded index: name+description+type, newest first. */
|
|
46
|
+
index(opts?: ListOptions): Promise<IndexRow[]>;
|
|
47
|
+
/** Fetch one memory by name (with full body). */
|
|
48
|
+
get(name: string): Promise<MemoryRow | null>;
|
|
65
49
|
/** Keyword recall, BM25-ranked, most relevant first. */
|
|
66
50
|
recall(query: string, opts?: RecallOptions): Promise<MemoryRow[]>;
|
|
67
|
-
/** Most recent memory rows. */
|
|
68
|
-
recent(opts?: ListOptions): Promise<MemoryRow[]>;
|
|
69
|
-
/** Memory rows carrying a given tag. */
|
|
70
|
-
byTag(tag: string, opts?: ListOptions): Promise<MemoryRow[]>;
|
|
71
|
-
/** Fetch one memory row by id, or null. */
|
|
72
|
-
get(id: string): Promise<MemoryRow | null>;
|
|
73
51
|
/** The subgraph around an entity: reachable entities + the edges among them. */
|
|
74
52
|
neighbors(name: string, opts?: {
|
|
75
53
|
depth?: number;
|
|
@@ -78,10 +56,26 @@ declare class ContextStore {
|
|
|
78
56
|
entities: Entity[];
|
|
79
57
|
edges: Edge[];
|
|
80
58
|
}>;
|
|
81
|
-
/** Counts:
|
|
59
|
+
/** Counts: memories, entities, edges. */
|
|
82
60
|
stats(): Promise<ContextStats>;
|
|
83
61
|
}
|
|
84
62
|
/** Wrap a PerSQL database handle as a shared context store. */
|
|
85
63
|
declare function context(db: PerSQLDatabase, opts?: ContextStoreOptions): ContextStore;
|
|
64
|
+
/** Tool definition shape for the OpenAI Agents SDK (`@openai/agents`). */
|
|
65
|
+
interface MemoryTool {
|
|
66
|
+
type: "function";
|
|
67
|
+
name: string;
|
|
68
|
+
description: string;
|
|
69
|
+
parameters: Record<string, unknown>;
|
|
70
|
+
invoke: (input: Record<string, unknown>) => Promise<unknown>;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Returns three memory tools ready to spread into an OpenAI Agents SDK
|
|
74
|
+
* `Agent({ tools })` call: `remember_memory`, `recall_memory`, `forget_memory`.
|
|
75
|
+
*
|
|
76
|
+
* The store must already be initialised (`await store.init()`) before the
|
|
77
|
+
* first tool call.
|
|
78
|
+
*/
|
|
79
|
+
declare function memoryTools(store: ContextStore): MemoryTool[];
|
|
86
80
|
|
|
87
|
-
export { type ContextStats, ContextStore, type ContextStoreOptions, Edge, Entity, ExtractedContext,
|
|
81
|
+
export { type ContextStats, ContextStore, type ContextStoreOptions, Edge, Entity, ExtractedContext, IndexRow, ListOptions, MemoryRow, type MemoryTool, RecallOptions, RememberInput, type WriteResult, context, memoryTools };
|