@promptev/context-engine 0.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.
Files changed (68) hide show
  1. package/LICENSE.md +202 -0
  2. package/NOTICE +17 -0
  3. package/README.md +112 -0
  4. package/dist/cli.js +11998 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/config-Bl9U789m.d.cts +174 -0
  7. package/dist/config-Bt9bUQqU.d.ts +174 -0
  8. package/dist/embeddings-B-jZ42mk.d.cts +67 -0
  9. package/dist/embeddings-DaSdAZN3.d.ts +67 -0
  10. package/dist/express.cjs +3173 -0
  11. package/dist/express.cjs.map +1 -0
  12. package/dist/express.d.cts +24 -0
  13. package/dist/express.d.ts +24 -0
  14. package/dist/express.js +3170 -0
  15. package/dist/express.js.map +1 -0
  16. package/dist/fastify.cjs +3184 -0
  17. package/dist/fastify.cjs.map +1 -0
  18. package/dist/fastify.d.cts +16 -0
  19. package/dist/fastify.d.ts +16 -0
  20. package/dist/fastify.js +3181 -0
  21. package/dist/fastify.js.map +1 -0
  22. package/dist/governance-BDkcv4qZ.d.cts +79 -0
  23. package/dist/governance-XIScatRO.d.ts +79 -0
  24. package/dist/graph/index.cjs +1428 -0
  25. package/dist/graph/index.cjs.map +1 -0
  26. package/dist/graph/index.d.cts +104 -0
  27. package/dist/graph/index.d.ts +104 -0
  28. package/dist/graph/index.js +1413 -0
  29. package/dist/graph/index.js.map +1 -0
  30. package/dist/hono.cjs +3183 -0
  31. package/dist/hono.cjs.map +1 -0
  32. package/dist/hono.d.cts +39 -0
  33. package/dist/hono.d.ts +39 -0
  34. package/dist/hono.js +3179 -0
  35. package/dist/hono.js.map +1 -0
  36. package/dist/index.cjs +11731 -0
  37. package/dist/index.cjs.map +1 -0
  38. package/dist/index.d.cts +851 -0
  39. package/dist/index.d.ts +851 -0
  40. package/dist/index.js +11676 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/mcp.cjs +181 -0
  43. package/dist/mcp.cjs.map +1 -0
  44. package/dist/mcp.d.cts +26 -0
  45. package/dist/mcp.d.ts +26 -0
  46. package/dist/mcp.js +179 -0
  47. package/dist/mcp.js.map +1 -0
  48. package/dist/migrations/sql/0001.sql +119 -0
  49. package/dist/migrations/sql/0002_graph.sql +48 -0
  50. package/dist/migrations/sql/0003_tools.sql +61 -0
  51. package/dist/migrations/sql/0004_acl_indexes.sql +4 -0
  52. package/dist/redaction-BmDSWJ7h.d.cts +98 -0
  53. package/dist/redaction-BmDSWJ7h.d.ts +98 -0
  54. package/dist/redaction-presidio.cjs +79 -0
  55. package/dist/redaction-presidio.cjs.map +1 -0
  56. package/dist/redaction-presidio.d.cts +22 -0
  57. package/dist/redaction-presidio.d.ts +22 -0
  58. package/dist/redaction-presidio.js +73 -0
  59. package/dist/redaction-presidio.js.map +1 -0
  60. package/dist/router-CrxZ2y_Z.d.ts +82 -0
  61. package/dist/router-OPgSoYAB.d.cts +82 -0
  62. package/dist/skills/context-engine/SKILL.md +160 -0
  63. package/package.json +184 -0
  64. package/src/migrations/sql/0001.sql +119 -0
  65. package/src/migrations/sql/0002_graph.sql +48 -0
  66. package/src/migrations/sql/0003_tools.sql +61 -0
  67. package/src/migrations/sql/0004_acl_indexes.sql +4 -0
  68. package/src/skills/context-engine/SKILL.md +160 -0
@@ -0,0 +1,851 @@
1
+ import { Pool } from 'pg';
2
+ import { C as ContextEngineConfig, L as LLMConfig, R as RerankerConfig } from './config-Bl9U789m.cjs';
3
+ export { a as ContextEngineConfigInit, E as EmbeddingConfig, F as FusionConfig, G as GraphConfig, S as StorageConfig } from './config-Bl9U789m.cjs';
4
+ import { H as Hooks, R as RedactionPolicy, U as UsageEvent, I as IngestReport } from './redaction-BmDSWJ7h.cjs';
5
+ export { D as DocumentReport, a as RedactionRule, b as RedactionRuleInit, c as applyRedaction, e as emitError, d as emitToolCall, f as emitUsage, g as graphUnits, u as unitsForFile } from './redaction-BmDSWJ7h.cjs';
6
+ import { E as Embedder, F as FetchImpl } from './embeddings-B-jZ42mk.cjs';
7
+ export { a as EmbedKind, b as buildEmbedder } from './embeddings-B-jZ42mk.cjs';
8
+ import { T as ToolEngine, C as CanonicalTool, b as ToolHttpClient, a as ToolConfig } from './governance-BDkcv4qZ.cjs';
9
+ export { c as ToolKind, d as configSchema } from './governance-BDkcv4qZ.cjs';
10
+ export { createMcpApp } from './mcp.cjs';
11
+ import 'zod';
12
+ import 'node:http';
13
+
14
+ interface ChunkRow {
15
+ idx: number;
16
+ text: string;
17
+ lang?: string | null;
18
+ embedding?: number[] | null;
19
+ meta?: Record<string, unknown>;
20
+ }
21
+ interface SearchScope {
22
+ sourceIds?: string[] | null;
23
+ principals?: string[] | null;
24
+ limit?: number;
25
+ }
26
+ interface StorageBackend {
27
+ supportsFts: boolean;
28
+ supportsTrgm: boolean;
29
+ supportsAnn: boolean;
30
+ upsertChunks(documentId: string, sourceId: string | null, acl: string[] | null, chunks: readonly ChunkRow[]): Promise<number>;
31
+ deleteChunks(documentId: string): Promise<void>;
32
+ updateChunkEmbeddings(documentId: string, vectors: readonly number[][]): Promise<number>;
33
+ updateChunkAcl(documentId: string, acl: string[] | null): Promise<number>;
34
+ filterIds(chunkIds: readonly string[], scope: SearchScope): Promise<string[]>;
35
+ ftsSearch(query: string, scope: SearchScope): Promise<string[]>;
36
+ trgmSearch(query: string, scope: SearchScope): Promise<string[]>;
37
+ annSearch(vector: readonly number[], scope: SearchScope): Promise<string[]>;
38
+ }
39
+ declare class PostgresBackend implements StorageBackend {
40
+ supportsFts: boolean;
41
+ supportsTrgm: boolean;
42
+ supportsAnn: boolean;
43
+ private readonly pool;
44
+ private readonly exactThresholdOverride;
45
+ /** Tri-state: null = not yet checked. */
46
+ private iterativeScan;
47
+ constructor(pool: Pool, exactThreshold?: number | null);
48
+ private get exactThreshold();
49
+ /**
50
+ * Bind values for SCOPE + the row limit.
51
+ * `principals` is passed through UNCHANGED: null (trusted) and [] (anonymous)
52
+ * mean different things.
53
+ */
54
+ private scopeParams;
55
+ upsertChunks(documentId: string, sourceId: string | null, acl: string[] | null, chunks: readonly ChunkRow[]): Promise<number>;
56
+ deleteChunks(documentId: string): Promise<void>;
57
+ updateChunkEmbeddings(documentId: string, vectors: readonly number[][]): Promise<number>;
58
+ updateChunkAcl(documentId: string, acl: string[] | null): Promise<number>;
59
+ filterIds(chunkIds: readonly string[], scope: SearchScope): Promise<string[]>;
60
+ ftsSearch(query: string, scope: SearchScope): Promise<string[]>;
61
+ trgmSearch(query: string, scope: SearchScope): Promise<string[]>;
62
+ /**
63
+ * Set pg_trgm's similarity threshold for this query via the `set_limit()` /
64
+ * `show_limit()` FUNCTIONS — never the GUC `pg_trgm.similarity_threshold`.
65
+ *
66
+ * The GUC is registered lazily, only once pg_trgm's shared library is loaded
67
+ * into the backend process. On a fresh pooled connection that has not yet
68
+ * run any pg_trgm operation, `SHOW pg_trgm.similarity_threshold` raises
69
+ * "unrecognized configuration parameter" — and because that error aborts the
70
+ * surrounding transaction, a fallback issued on the same connection then
71
+ * fails with "current transaction is aborted". Every connection a pool hands
72
+ * out is fresh, so this is the common case, not an edge case. Calling
73
+ * `set_limit()` loads the library and does not depend on the GUC. This
74
+ * mirrors the Python backend, which was hardened against exactly this.
75
+ *
76
+ * `set_limit()` is connection-scoped and survives COMMIT and the return to
77
+ * the pool, so the previous value is captured here and restored by the
78
+ * caller.
79
+ */
80
+ private applyTrgmThreshold;
81
+ private restoreTrgmLimit;
82
+ annSearch(vector: readonly number[], scope: SearchScope): Promise<string[]>;
83
+ private eligibleIsSmall;
84
+ private tuneAnnScan;
85
+ private scanBudget;
86
+ private enableIterativeScan;
87
+ private supportsIterativeScan;
88
+ }
89
+
90
+ type Mode$1 = "hybrid" | "graph";
91
+ interface IngestRequest {
92
+ content?: Buffer | null;
93
+ filename?: string | null;
94
+ text?: string | null;
95
+ name?: string | null;
96
+ description?: string | null;
97
+ sourceId?: string | null;
98
+ externalId?: string | null;
99
+ metaData?: Record<string, unknown> | null;
100
+ acl?: string[] | null;
101
+ mode?: Mode$1;
102
+ batch?: boolean;
103
+ extractStructured?: boolean;
104
+ fieldHints?: Record<string, unknown>[] | null;
105
+ }
106
+
107
+ /**
108
+ * CPSE action surface — document access, structured query, compute.
109
+ *
110
+ * **ACL/source scoping.** Every read here goes through `scopeDocumentsSql`,
111
+ * the Document-table equivalent of storage's chunk-table predicate:
112
+ * `sourceIds=null` means the whole corpus, a list scopes to it;
113
+ * `principals=null` means a trusted internal caller (no ACL filtering),
114
+ * `principals=[]` means an anonymous caller and matches only `acl IS NULL`
115
+ * documents. Same non-interchangeable pair as `search()` — see that
116
+ * module's docstring.
117
+ *
118
+ * Missing vs forbidden documents raise `DocumentNotFoundError` with the
119
+ * identical message `document not found: ${id}` so a caller can never
120
+ * distinguish "doesn't exist" from "exists but you can't see it".
121
+ */
122
+
123
+ /**
124
+ * Return one document's full text, ACL-checked.
125
+ *
126
+ * Unlike an LLM-tool payload helper, this returns the WHOLE text
127
+ * untruncated — token-budget policy belongs to whatever calls this.
128
+ *
129
+ * Raises `EngineActionError` for an unparseable id and
130
+ * `DocumentNotFoundError` for a missing document or one the caller's
131
+ * `principals` can't see — the same message for the last two, deliberately.
132
+ *
133
+ * `redaction` (an `apply_at="output"` policy) masks the fetched text
134
+ * before it is returned. This is the ONLY protection for a corpus ingested
135
+ * WITHOUT an `apply_at="ingest"` policy.
136
+ */
137
+ declare function getDocumentText(documentId: string, opts: {
138
+ pool: Pool;
139
+ principals?: string[] | null;
140
+ redaction?: RedactionPolicy | null;
141
+ secretKey?: string | Buffer | null;
142
+ hooks?: Hooks;
143
+ }): Promise<string>;
144
+ /**
145
+ * Page through documents in scope, newest first.
146
+ *
147
+ * `redaction` masks `documentType` — unconstrained free text an LLM wrote
148
+ * after reading the document body. `name`/`description` remain
149
+ * intentionally unmasked: both are CALLER-set at ingest time, not text the
150
+ * pipeline derived from the document body.
151
+ */
152
+ declare function listDocuments(opts: {
153
+ pool: Pool;
154
+ sourceId?: string | null;
155
+ principals?: string[] | null;
156
+ cursor?: unknown;
157
+ limit?: number;
158
+ redaction?: RedactionPolicy | null;
159
+ secretKey?: string | Buffer | null;
160
+ hooks?: Hooks;
161
+ }): Promise<Record<string, unknown>>;
162
+ /**
163
+ * Answer a natural-language question against documents' `structuredData`.
164
+ *
165
+ * When NOTHING resolves — every candidate fell below the similarity floor
166
+ * and had no exact/synonym match either — this returns ZERO documents, not
167
+ * the whole in-scope corpus. An off-topic question has no business getting
168
+ * an invoice back just because the invoice happens to have SOME structured
169
+ * fields.
170
+ *
171
+ * `redaction` masks each result's `documentType` and the STRING VALUES of
172
+ * `structuredData`. KEYS are left unredacted — they are the canonical
173
+ * field names this function's `resolveFields` matching keys off of.
174
+ */
175
+ declare function queryStructured(question: string, opts: {
176
+ pool: Pool;
177
+ embedder: Embedder;
178
+ sourceIds?: string[] | null;
179
+ principals?: string[] | null;
180
+ docType?: string | null;
181
+ limit?: number;
182
+ redaction?: RedactionPolicy | null;
183
+ secretKey?: string | Buffer | null;
184
+ hooks?: Hooks;
185
+ }): Promise<Record<string, unknown>>;
186
+ /**
187
+ * Compute an answer to `instruction` over in-scope spreadsheet documents.
188
+ *
189
+ * **Disabled by default** (`config.enableCodeExecution`): this executes
190
+ * LLM-GENERATED code. Raises `EngineActionError` before ANY work — no LLM
191
+ * call, no sandbox run — when the flag is `false`. That flag, not the
192
+ * isolate, is the actual security boundary.
193
+ *
194
+ * `config.redaction` is applied TWICE:
195
+ * 1. Each document's raw `.text` is masked BEFORE it is parsed into a
196
+ * table, so the data the LLM-authored code runs against is built from
197
+ * masked cells.
198
+ * 2. The final returned dict is swept whole through `redactValueRecursive`
199
+ * — including `code`.
200
+ */
201
+ declare function compute(instruction: string, opts: {
202
+ pool: Pool;
203
+ config: ContextEngineConfig;
204
+ hooks: Hooks;
205
+ sourceIds?: string[] | null;
206
+ principals?: string[] | null;
207
+ docIds?: string[] | null;
208
+ modelCfg?: LLMConfig | null;
209
+ timeout?: number;
210
+ }): Promise<Record<string, unknown>>;
211
+
212
+ /**
213
+ * AES-256-GCM crypto seam. Wire format is a compatibility promise with the
214
+ * Python library and promptev-connectors:
215
+ * base64(nonce[12] || AES-256-GCM ciphertext+tag)
216
+ */
217
+ declare function encryptDict(data: Record<string, unknown>, key: Buffer): string;
218
+ declare function decryptDict(token: string, key: Buffer): Record<string, unknown>;
219
+ declare function getSecretKey(config: {
220
+ secretKey?: string | null;
221
+ }): Buffer;
222
+
223
+ declare function runMigrate(databaseUrl: string, opts?: {
224
+ dim?: number;
225
+ graph?: boolean;
226
+ }): Promise<void>;
227
+
228
+ /** Action-surface errors — user-facing / data-dependent failures (not found, ACL, bad input).
229
+ * Config/programming errors stay as TypeError / RangeError / Error.
230
+ */
231
+ declare class EngineActionError extends Error {
232
+ constructor(message: string);
233
+ }
234
+ /** Missing vs forbidden document: identical message so callers cannot distinguish. */
235
+ declare class DocumentNotFoundError extends Error {
236
+ constructor(documentId: string);
237
+ }
238
+ declare class GraphLegUnavailable extends Error {
239
+ constructor(message?: string);
240
+ }
241
+ declare class ApprovalNotPending extends Error {
242
+ constructor(message?: string);
243
+ }
244
+ declare class ApprovalExpired extends Error {
245
+ constructor(message?: string);
246
+ }
247
+ declare class CodeExecutionError extends Error {
248
+ constructor(message: string);
249
+ }
250
+ declare class CodeExecutionTimeout extends Error {
251
+ constructor(message?: string);
252
+ }
253
+ declare class ExtraMissingError extends Error {
254
+ constructor(extra: string, pkg: string, what: string);
255
+ }
256
+
257
+ /**
258
+ * Hybrid retrieval: run the legs, fuse, (rerank), hydrate, (compress).
259
+ *
260
+ * `ContextEngine.search()` is a thin shell over `runSearch()` here. One call
261
+ * goes through these stages:
262
+ *
263
+ * embed query → run supported legs concurrently → RRF fuse →
264
+ * hydrate candidates → redact BEFORE rerank → optional rerank →
265
+ * cut to top_k → optional compression → usage event
266
+ *
267
+ * **ACL semantics — read before touching the scope.** `principals=null` means
268
+ * an internal, fully-trusted caller and disables ACL filtering completely;
269
+ * `principals=[]` means an anonymous caller and matches only chunks whose
270
+ * `acl IS NULL`. Those are not interchangeable. The HTTP router ALWAYS
271
+ * passes a list — never `null`, never a `null` that leaked out of an
272
+ * absent auth header — so an unauthenticated request can never be mistaken
273
+ * for a trusted one. `sourceIds=null` likewise means "the whole corpus".
274
+ *
275
+ * Failure policy: retrieval degrades rather than dies. A query that can't be
276
+ * embedded (provider down) reports through `emitError` and continues with
277
+ * the lexical legs; one leg raising reports and continues with the others; a
278
+ * reranker or compression failure reports and falls back to the fused order.
279
+ * Only a search where EVERY attempted leg failed raises — at that point there
280
+ * is no result to degrade to, and returning an empty list would look like
281
+ * "nothing matched".
282
+ */
283
+
284
+ type Mode = "hybrid" | "graph";
285
+ /**
286
+ * One retrieved chunk, hydrated with its document's identity.
287
+ *
288
+ * **`score` is provenance, not the ranking key.** It is always the RRF
289
+ * fusion score. When a reranker runs, the reranker decides the ORDER while
290
+ * the score keeps its fusion value — `providers.reranker.rerank()` returns
291
+ * positions only, never scores, so there is no reranker score to put here
292
+ * and inventing one would misrepresent a number no provider gave us.
293
+ * Consequence for consumers: **the list order is authoritative — re-sorting
294
+ * hits by `score` silently discards the reranker's ordering.** Check
295
+ * `SearchResult.usage.reranked` if you need to know which applies.
296
+ */
297
+ interface Hit {
298
+ documentId: string;
299
+ documentName: string | null;
300
+ description: string | null;
301
+ sourceId: string | null;
302
+ chunkText: string;
303
+ idx: number | null;
304
+ lang: string | null;
305
+ score: number;
306
+ chunkId: string;
307
+ meta: Record<string, unknown>;
308
+ }
309
+ interface SearchResult {
310
+ hits: Hit[];
311
+ usage: Record<string, unknown>;
312
+ }
313
+ interface RunSearchOpts {
314
+ config: ContextEngineConfig;
315
+ hooks: Hooks;
316
+ backend: StorageBackend;
317
+ embedder: Embedder;
318
+ pool: Pool;
319
+ sourceIds?: string[] | null;
320
+ principals?: string[] | null;
321
+ topK?: number;
322
+ mode?: Mode;
323
+ compressToTokens?: number | null;
324
+ graphRanked?: string[] | null;
325
+ redaction?: RedactionPolicy | null;
326
+ }
327
+ /**
328
+ * Redact every hit's `chunkText`, and any string `meta` values, in
329
+ * place-by-copy.
330
+ *
331
+ * `meta` is in scope for the same reason the ingest-side fix
332
+ * (`redactChunkMeta`) exists: `Hit.meta` (`section_title`, `sheet_title`,
333
+ * ...) is extracted verbatim from the same raw document text as
334
+ * `chunkText` and is returned to the caller right alongside it. Ingest-time
335
+ * redaction only masks `meta` for corpora ingested UNDER a policy with
336
+ * ingest-phase rules; an output-phase policy is the ONLY protection for a
337
+ * corpus ingested WITHOUT one — which is the main use case output rules
338
+ * exist for. Skipping `meta` here would leave that corpus's PII reachable
339
+ * through a field sitting right next to the text we just masked, repeating
340
+ * the exact gap the ingest fix closed. `documentName`/`description` are
341
+ * left untouched: they are uploader/admin-set Document columns, not text
342
+ * the chunker derived from the document body, so they are out of scope for
343
+ * a chunk-content redaction pass.
344
+ *
345
+ * Returns `[hits, note]`; `note` is `{}` when nothing fired. Aggregates
346
+ * `rules_fired` (union, order preserved) and `spans` (sum) across hits and
347
+ * across `chunkText` + `meta`.
348
+ *
349
+ * `runSearch` calls this BEFORE maybeRerank/maybeCompress by design (see the
350
+ * call site), so when a policy is active the reranker scores MASKED text
351
+ * and the compressor counts MASKED tokens, not the original — this can
352
+ * shift relevance ordering and token-budget decisions versus the no-policy
353
+ * case. That is the correct, intended trade-off (redaction is not optional
354
+ * just because it might reorder results), but it is worth stating
355
+ * explicitly: a later "reranking got worse" or "compression drops the
356
+ * wrong hit" report on a redacted corpus may be this, not a
357
+ * reranker/compressor regression.
358
+ *
359
+ * FLAT walk, deliberately — not the recursive walker
360
+ * `governance.redactToolResult`/`actions.redactValueRecursive` use. Every
361
+ * `chunk_metas` producer in `chunkers.ts` emits a flat
362
+ * `{str: str | int | bool}` dict today (`section_title`, `sheet_title`,
363
+ * `rows_range`, `total_rows`, ...), so a one-level walk covers every string
364
+ * leaf that actually exists. Final review flagged this as a silent-bypass
365
+ * risk if a FUTURE chunker ever nests `meta` — reviewed and accepted
366
+ * rather than switched to a recursive walk, because threading
367
+ * `fired`/`failed` aggregation through a shared recursive primitive across
368
+ * three call sites with different existing note shapes was judged
369
+ * higher-risk than the flat assumption it would replace.
370
+ */
371
+ declare function redactHits(hits: Hit[], policy: RedactionPolicy, opts: {
372
+ principals: string[] | null;
373
+ secretKey: string | Buffer | null;
374
+ hooks?: Hooks | null;
375
+ }): [Hit[], Record<string, unknown>];
376
+ /**
377
+ * Hybrid retrieval over one Context Engine database.
378
+ *
379
+ * `graphRanked` is the Task 14 seam: when the graph stage exists it
380
+ * passes its own ranked chunk-id list here and fusion picks it up as a
381
+ * fourth leg (weight `fusion.weights.graph`), which is also what flips
382
+ * the billing to `UNITS_PER_SCOPE_GRAPH`. Those ids are re-filtered through
383
+ * `backend.filterIds` first — they come from Neo4j, not from a leg's WHERE
384
+ * clause, so they carry no ACL or source guarantee of their own.
385
+ *
386
+ * `mode="graph"` therefore runs the hybrid legs and reports
387
+ * `usage.graphLeg = false` with `usage.degraded = "graph_leg_unavailable"`
388
+ * (plus one `emitError`), rather than letting `mode` alone imply a leg
389
+ * that never ran. `graphLeg` is the truth of whether the graph leg
390
+ * CONTRIBUTED, not merely whether it was attempted — `filterIds` may have
391
+ * emptied the list (every id ACL/source-filtered out).
392
+ */
393
+ declare function runSearch(query: string, opts: RunSearchOpts): Promise<SearchResult>;
394
+
395
+ /**
396
+ * Sentinels distinguishing omitted arguments from real values, including null.
397
+ *
398
+ * UNSET: "argument omitted" vs null (e.g. updateDocument acl=null means unrestricted).
399
+ * TRUSTED: trusted caller, ACL filtering disabled. Truthy on purpose so
400
+ * `if (principals)` does not treat a trusted caller as anonymous.
401
+ */
402
+ declare const UNSET: unique symbol;
403
+ type Unset = typeof UNSET;
404
+ declare class TrustedSentinel {
405
+ readonly [Symbol.toStringTag] = "TRUSTED";
406
+ toString(): string;
407
+ valueOf(): boolean;
408
+ }
409
+ declare const TRUSTED: TrustedSentinel;
410
+ type Trusted = typeof TRUSTED;
411
+ type Principals = string[] | null | typeof TRUSTED | undefined;
412
+ declare function resolvePrincipals(value: Principals, method: string): string[] | null;
413
+
414
+ /**
415
+ * `ContextEngine` — the package's public object.
416
+ *
417
+ * Construction is cheap and side-effect free: no database connection is
418
+ * opened and no provider client is built until the first call that needs
419
+ * one (the embedder in particular is built lazily, so an engine used only
420
+ * for `getDocument`/`stats` never constructs an LLM/embedding HTTP client).
421
+ *
422
+ * Graph mode is gated behind `GraphConfig.enabled` and the optional graph
423
+ * extra. A graph-retrieval error at query time degrades to hybrid-only
424
+ * results (visible via `usage.degraded`) rather than failing the search.
425
+ */
426
+
427
+ declare class ContextEngine implements ToolEngine {
428
+ config: ContextEngineConfig;
429
+ hooks: Hooks;
430
+ pool: Pool;
431
+ backend: StorageBackend | null;
432
+ /** In-memory function-tool registry. Function tools are NOT persisted. */
433
+ _functionTools: Record<string, CanonicalTool>;
434
+ _toolHttpClient: ToolHttpClient | null;
435
+ private _pool;
436
+ private _embedder;
437
+ /** Built lazily on first graph use so constructing an engine never pulls neo4j. */
438
+ private _graphStore;
439
+ constructor(config: ContextEngineConfig, opts?: {
440
+ onUsage?: ((e: UsageEvent) => void) | null;
441
+ onError?: ((e: unknown, ctx: Record<string, unknown>) => void) | null;
442
+ });
443
+ private ensurePool;
444
+ /** The embedding client, built on first use. */
445
+ get embedder(): Embedder;
446
+ private getGraphStore;
447
+ private runGraphStage;
448
+ /**
449
+ * Release everything this engine opened. Safe to call more than once.
450
+ *
451
+ * Construction is lazy, so this only tears down what was actually built.
452
+ * Also usable via `await using engine = new ContextEngine(config)`.
453
+ */
454
+ aclose(): Promise<void>;
455
+ [Symbol.asyncDispose](): Promise<void>;
456
+ /**
457
+ * Ingest one document. Returns its `IngestReport` (never raises for
458
+ * per-document failures — those come back as `status="failed"`).
459
+ *
460
+ * Exactly one of `file` / `content` / `text` must be given. `text=`
461
+ * skips extraction. `mode=null` uses `config.defaultMode`; `"graph"`
462
+ * requires `config.graph.enabled`.
463
+ */
464
+ ingest(opts?: IngestRequest & {
465
+ file?: unknown;
466
+ content?: Buffer | null;
467
+ filename?: string | null;
468
+ text?: string | null;
469
+ }): Promise<IngestReport>;
470
+ /**
471
+ * Poll every `batch_pending` document; fill embeddings for finished jobs.
472
+ *
473
+ * There is NO background daemon — `ingest({ batch: true })` submits the
474
+ * embedding job and returns immediately; something has to call this
475
+ * method afterwards. Returns the number of documents flipped to
476
+ * `completed` in this call.
477
+ */
478
+ resumeBatches(): Promise<number>;
479
+ /**
480
+ * Retrieve the `top_k` chunks most relevant to `query`.
481
+ *
482
+ * `principals` is the ACL contract and the two "empty" values are NOT
483
+ * the same: `null`/`TRUSTED` means an internal, fully-trusted caller and
484
+ * skips ACL filtering entirely, while `[]` means an anonymous caller and
485
+ * matches only chunks with no ACL.
486
+ */
487
+ search(query: string, opts?: {
488
+ sourceIds?: string[] | null;
489
+ principals?: Principals;
490
+ topK?: number;
491
+ mode?: "hybrid" | "graph";
492
+ compressToTokens?: number | null;
493
+ redaction?: RedactionPolicy | null;
494
+ }): Promise<SearchResult>;
495
+ /**
496
+ * Return one document as a plain dict (raises `DocumentNotFoundError` if
497
+ * absent OR not visible under `principals` — identical message, so a
498
+ * caller can never distinguish "doesn't exist" from "exists but you
499
+ * can't see it").
500
+ */
501
+ getDocument(documentId: string, opts?: {
502
+ principals?: Principals;
503
+ }): Promise<Record<string, unknown>>;
504
+ deleteDocument(documentId: string, opts?: {
505
+ principals?: Principals;
506
+ }): Promise<void>;
507
+ /**
508
+ * Update caller-declared attributes WITHOUT re-sending content.
509
+ *
510
+ * PATCH semantics: only the fields actually passed change. `acl=null`
511
+ * UNRESTRICTS — it can never mean "leave alone"; omit the argument
512
+ * entirely for that (the `UNSET` sentinel is what makes the two
513
+ * distinguishable). A PATCH that includes `acl` always re-syncs the
514
+ * chunk plane, even when the value matches what's already stored.
515
+ */
516
+ updateDocument(documentId: string, opts?: {
517
+ acl?: string[] | null | Unset;
518
+ name?: string | null | Unset;
519
+ description?: string | null | Unset;
520
+ metaData?: Record<string, unknown> | null | Unset;
521
+ principals?: Principals;
522
+ }): Promise<string[]>;
523
+ stats(sourceId?: string | null): Promise<Record<string, unknown>>;
524
+ getDocumentText(documentId: string, opts?: {
525
+ principals?: Principals;
526
+ }): Promise<string>;
527
+ listDocuments(opts?: {
528
+ sourceId?: string | null;
529
+ principals?: Principals;
530
+ cursor?: unknown;
531
+ limit?: number;
532
+ }): Promise<Record<string, unknown>>;
533
+ queryStructured(question: string, opts?: {
534
+ sourceIds?: string[] | null;
535
+ principals?: Principals;
536
+ docType?: string | null;
537
+ limit?: number;
538
+ }): Promise<Record<string, unknown>>;
539
+ compute(instruction: string, opts?: {
540
+ sourceIds?: string[] | null;
541
+ principals?: Principals;
542
+ docIds?: string[] | null;
543
+ modelCfg?: LLMConfig | null;
544
+ timeout?: number;
545
+ }): Promise<Record<string, unknown>>;
546
+ registerTool(tc: ToolConfig): Promise<string>;
547
+ registerFunctionTool(fn: (...args: never[]) => unknown): string;
548
+ updateTool(id: string, opts?: Record<string, unknown> & {
549
+ principals?: Principals;
550
+ }): Promise<ToolConfig>;
551
+ deleteTool(id: string, opts?: {
552
+ principals?: Principals;
553
+ }): Promise<void>;
554
+ listTools(opts?: {
555
+ sourceId?: string | null;
556
+ principals?: Principals;
557
+ }): Promise<Record<string, unknown>[]>;
558
+ searchTools(query: string, opts?: {
559
+ sourceId?: string | null;
560
+ principals?: Principals;
561
+ limit?: number;
562
+ }): Promise<Record<string, unknown>[]>;
563
+ testTool(tc: ToolConfig): Promise<Record<string, unknown>>;
564
+ executeTool(callName: string, args: Record<string, unknown> | null, opts?: {
565
+ sourceId?: string | null;
566
+ principals?: Principals;
567
+ actor?: Record<string, unknown> | null;
568
+ source?: string;
569
+ }): Promise<Record<string, unknown>>;
570
+ }
571
+
572
+ /**
573
+ * File -> text extraction. `extract()` is the single public entry point.
574
+ *
575
+ * Never throws for extraction failures internal to a single file — those are
576
+ * reported via `hooks.onError` and degrade to whatever partial text was recovered.
577
+ */
578
+
579
+ declare class Extracted {
580
+ text: string;
581
+ pages: number | null;
582
+ slides: number | null;
583
+ mediaOnly: boolean;
584
+ providerTokens: Record<string, number>;
585
+ llmPictures: number;
586
+ isMarkdown: boolean;
587
+ constructor(init: {
588
+ text: string;
589
+ pages?: number | null;
590
+ slides?: number | null;
591
+ mediaOnly?: boolean;
592
+ providerTokens?: Record<string, number>;
593
+ llmPictures?: number;
594
+ isMarkdown?: boolean;
595
+ });
596
+ }
597
+ /**
598
+ * Extract text from `content` (raw file bytes). Never throws for per-file
599
+ * extraction failures — those go through `hooks.onError` and degrade.
600
+ */
601
+ declare function extract(content: Buffer, filename: string, mime: string | null, opts: {
602
+ visionLlm?: LLMConfig | null;
603
+ hooks: Hooks;
604
+ }): Promise<Extracted>;
605
+
606
+ declare const DEFAULT_LEG_WEIGHT = 1;
607
+ /**
608
+ * Reciprocal Rank Fusion. score(id) = Σ weight/(k+rank) with 1-based rank.
609
+ * Missing weight → 1.0; explicit 0.0 skips the leg; first occurrence wins;
610
+ * ties broken by chunk id ascending.
611
+ */
612
+ declare function rrfFuse(rankedLists: Record<string, string[]>, opts: {
613
+ k: number;
614
+ weights: Record<string, number>;
615
+ }): Array<[string, number]>;
616
+
617
+ /**
618
+ * Pluggable LLM provider.
619
+ *
620
+ * `callLlm(cfg, { system, user, jsonMode, images, client })` is the single entry
621
+ * point. Returns `[text, { input, output }]`.
622
+ *
623
+ * Images are always PNG. 30s timeout, no retries. Injectable `client` / `fetch`.
624
+ * `callLlm` owns and closes the client it builds unless one is passed in.
625
+ */
626
+
627
+ type TokenUsage = {
628
+ input: number;
629
+ output: number;
630
+ };
631
+ type ImageBytes = Uint8Array;
632
+ type ChatClient = {
633
+ chat: {
634
+ completions: {
635
+ create(body: {
636
+ model: string;
637
+ messages: unknown[];
638
+ response_format?: {
639
+ type: string;
640
+ };
641
+ }): Promise<{
642
+ choices: Array<{
643
+ message?: {
644
+ content?: string | null;
645
+ };
646
+ }>;
647
+ usage?: {
648
+ prompt_tokens?: number;
649
+ completion_tokens?: number;
650
+ } | null;
651
+ }>;
652
+ };
653
+ };
654
+ close?: () => void | Promise<void>;
655
+ timeout?: number;
656
+ baseURL?: string;
657
+ };
658
+ type LlmCallOpts = {
659
+ system: string;
660
+ user: string;
661
+ jsonMode?: boolean;
662
+ images?: ImageBytes[] | null;
663
+ };
664
+ type LLMClientOpts = {
665
+ client?: ChatClient | null;
666
+ fetch?: FetchImpl | null;
667
+ fetchImpl?: FetchImpl | null;
668
+ };
669
+ declare class LLMClient {
670
+ cfg: LLMConfig;
671
+ provider: LLMConfig["provider"];
672
+ model: string;
673
+ client: ChatClient | null;
674
+ fetchImpl: FetchImpl | null;
675
+ private genaiClient;
676
+ constructor(cfg: LLMConfig, opts?: LLMClientOpts);
677
+ aclose(): Promise<void>;
678
+ [Symbol.asyncDispose](): Promise<void>;
679
+ call(opts: LlmCallOpts): Promise<[string, TokenUsage]>;
680
+ private callAnthropic;
681
+ private callOpenAI;
682
+ private callGemini;
683
+ private callBedrock;
684
+ }
685
+ declare function buildLlmClient(cfg: LLMConfig, opts?: LLMClientOpts): LLMClient;
686
+ /**
687
+ * Call the LLM configured by `cfg`. Returns `[text, tokenUsage]`.
688
+ *
689
+ * With no `client`, one is built for this call and CLOSED afterwards,
690
+ * success or failure. Pass `client` to reuse a connection across many calls;
691
+ * the caller then owns `aclose()`.
692
+ */
693
+ declare function callLlm(cfg: LLMConfig, opts: LlmCallOpts & {
694
+ client?: LLMClient | null;
695
+ }): Promise<[string, TokenUsage]>;
696
+
697
+ /**
698
+ * Pluggable reranker provider.
699
+ *
700
+ * Returns indices into `docs`, sorted most-relevant-first (re-sorted by score
701
+ * descending rather than trusting API order). 30s timeout, no retries.
702
+ * Injectable `fetch` for tests.
703
+ *
704
+ * custom POSTs `{ query, texts }` to `{baseUrl}/rerank`.
705
+ */
706
+
707
+ /**
708
+ * Rerank `docs` against `query`. Returns indices into `docs`, most-relevant-first.
709
+ * Raises if `cfg.enabled` is false.
710
+ */
711
+ declare function rerank(cfg: RerankerConfig, query: string, docs: string[], opts?: {
712
+ fetch?: FetchImpl | null;
713
+ fetchImpl?: FetchImpl | null;
714
+ }): Promise<number[]>;
715
+
716
+ interface TaskStatus {
717
+ state: "running" | "done" | "failed" | string;
718
+ error: string | null;
719
+ result: unknown;
720
+ }
721
+ interface TaskRunner {
722
+ /** Schedule `factory()` to run, returning a task id. Takes a zero-argument CALLABLE — not a bare Promise — because some runners need to construct it lazily. */
723
+ submit(factory: () => Promise<unknown>): string;
724
+ /** Return `{state, error, result}`. Throws for an unknown task id. */
725
+ status(id: string): TaskStatus;
726
+ }
727
+ declare class InProcessRunner implements TaskRunner {
728
+ private readonly registry;
729
+ /** Kept so the Promise isn't garbage-collected mid-flight. */
730
+ private readonly tasks;
731
+ submit(factory: () => Promise<unknown>): string;
732
+ status(id: string): TaskStatus;
733
+ }
734
+ /**
735
+ * Adapter seam for callers who already run a distributed task queue —
736
+ * NOT a working integration. `submit()`/`status()` throw so nobody
737
+ * mistakes this stub for something that works out of the box.
738
+ */
739
+ declare class CeleryRunner implements TaskRunner {
740
+ celeryApp: unknown;
741
+ taskName: string;
742
+ constructor(celeryApp: unknown, taskName: string);
743
+ submit(_factory?: () => Promise<unknown>): string;
744
+ status(_id?: string): TaskStatus;
745
+ }
746
+
747
+ /** Extraction schema version stamped on `documents.extraction_version`. */
748
+ declare const EXTRACTION_VERSION = "ce-structured-v1";
749
+ interface ExtractionResult {
750
+ documentType: string | null;
751
+ structuredDataRaw: Record<string, unknown>;
752
+ structuredData: Record<string, unknown>;
753
+ keysRaw: string[];
754
+ keysNormalized: string[];
755
+ quality: "high" | "medium" | "low" | "failed" | string;
756
+ error: string | null;
757
+ providerTokens: Record<string, number>;
758
+ }
759
+ /**
760
+ * Extract structured key/value data from already-extracted document text.
761
+ *
762
+ * Never throws — an LLM failure or unparseable response comes back as
763
+ * `ExtractionResult(quality="failed", error=...)` so ingest can complete
764
+ * the rest of ingestion instead of losing already-computed chunks.
765
+ *
766
+ * Tokens are tracked CUMULATIVELY across every attempt whose `callLlm`
767
+ * actually completed, even one whose response then failed to parse —
768
+ * that attempt still consumed real, billable provider tokens.
769
+ */
770
+ declare function extractStructuredData(text: string, opts: {
771
+ llmCfg: LLMConfig;
772
+ fieldHints?: Record<string, unknown>[] | null;
773
+ maxChars?: number;
774
+ maxRetries?: number;
775
+ }): Promise<ExtractionResult>;
776
+ /**
777
+ * Upsert `structuredData`'s keys into the field registry.
778
+ *
779
+ * Generates a field-name embedding (`Embedder.embed(..., kind="document")`)
780
+ * for any NEW field, or one whose synonym set changed, or one that somehow
781
+ * has no embedding yet — batched into one `embed()` call per ingest rather
782
+ * than one per field.
783
+ *
784
+ * Returns the embedding provider's token count (0 if nothing needed embedding).
785
+ */
786
+ declare function upsertRegistry(opts: {
787
+ pool: Pool;
788
+ embedder: Embedder;
789
+ sourceId: string | null;
790
+ docType: string;
791
+ structuredData: Record<string, unknown>;
792
+ keysRaw: string[];
793
+ keysNormalized: string[];
794
+ }): Promise<number>;
795
+ /**
796
+ * Resolve free-text field-name candidates to canonical registry keys.
797
+ *
798
+ * Cheapest-first: normalize → exact `key_norm` match → synonym-array
799
+ * match → nearest registry embedding (cosine, gated by
800
+ * FIELD_RESOLUTION_SIMILARITY_FLOOR). Unresolved candidates are simply
801
+ * absent from the returned `{candidate: canonicalKey}` mapping.
802
+ *
803
+ * All still-unresolved candidates are embedded in ONE batched `embed()` call.
804
+ *
805
+ * No ACL: registry rows are field NAMES, not document content, so source_id
806
+ * scoping is enough.
807
+ */
808
+ declare function resolveFields(candidates: readonly string[], opts: {
809
+ pool: Pool;
810
+ embedder: Embedder;
811
+ sourceIds?: string[] | null;
812
+ docType?: string | null;
813
+ }): Promise<Record<string, string>>;
814
+
815
+ interface ApprovalRecord {
816
+ id: string;
817
+ toolName: string;
818
+ toolArgsFrozen: Record<string, unknown>;
819
+ sourceId: string | null;
820
+ principals: unknown;
821
+ status: string;
822
+ approver: string | null;
823
+ approverMeta: Record<string, unknown> | null;
824
+ expiresAt: Date | null;
825
+ resolvedAt: Date | null;
826
+ createdAt: Date | null;
827
+ }
828
+ type EngineLike = {
829
+ pool: {
830
+ query: (sql: string, params?: unknown[]) => Promise<{
831
+ rows: Array<Record<string, unknown>>;
832
+ rowCount?: number | null;
833
+ }>;
834
+ };
835
+ };
836
+ declare function shouldRequireApproval(ct: Pick<CanonicalTool, "requiresApproval" | "approvalPolicy">, args: Record<string, unknown>): boolean;
837
+ declare function resolveApproval(engine: EngineLike, approvalId: string, decision: "approved" | "rejected", approver: string, meta?: Record<string, unknown> | null, opts?: {
838
+ principals?: string[] | null;
839
+ }): Promise<ApprovalRecord>;
840
+
841
+ /**
842
+ * Derive a CanonicalTool from a plain function's name and arity.
843
+ * `fn.length` is the count of required positional parameters (JS omits
844
+ * defaults from `.length`); parameter names are parsed from source.
845
+ */
846
+ declare function functionTool(fn: (...args: never[]) => unknown): CanonicalTool;
847
+
848
+ /** Bumped by CI on every main merge; 0.0.0 = pre-first-release. */
849
+ declare const __version__ = "0.0.0";
850
+
851
+ export { ApprovalExpired, ApprovalNotPending, type ApprovalRecord, CeleryRunner, type ChunkRow, CodeExecutionError, CodeExecutionTimeout, ContextEngine, ContextEngineConfig, DEFAULT_LEG_WEIGHT, DocumentNotFoundError, EXTRACTION_VERSION, Embedder, EngineActionError, ExtraMissingError, Extracted, type ExtractionResult, GraphLegUnavailable, type Hit, Hooks, InProcessRunner, IngestReport, LLMClient, LLMConfig, PostgresBackend, type Principals, RedactionPolicy, RerankerConfig, type SearchResult, type SearchScope, type StorageBackend, TRUSTED, type TaskRunner, type TaskStatus, ToolConfig, type Trusted, UNSET, type Unset, UsageEvent, __version__, buildLlmClient, callLlm, compute, decryptDict, encryptDict, extract, extractStructuredData, functionTool, getDocumentText, getSecretKey, listDocuments, queryStructured, redactHits, rerank, resolveApproval, resolveFields, resolvePrincipals, rrfFuse, runMigrate, runSearch, shouldRequireApproval, upsertRegistry };