@babav/knowledge-core-client 0.38.1 → 0.40.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/dist/index.d.ts CHANGED
@@ -211,6 +211,40 @@ export interface Corpus {
211
211
  name: string;
212
212
  description: string | null;
213
213
  custom_metadata: Record<string, unknown>;
214
+ profile_id: UUID | null;
215
+ }
216
+ /** Build-time knob bundle that DEFINES an index. Defaults are today's blessed config, so a
217
+ * profile created with no params is the blessed profile. Change any knob => different vectors
218
+ * => a different physical collection. Embedding/indexing/chunking is SYSTEM-level, never a
219
+ * query knob — this is the build-side counterpart to an Agent, not an agent. */
220
+ export interface IngestionProfileParams {
221
+ chunker: string;
222
+ chunk_child_max_tokens: number;
223
+ chunk_parent_max_tokens: number;
224
+ contextualize: boolean;
225
+ embedding_provider: string;
226
+ embedding_model: string;
227
+ embedding_dimensions: number;
228
+ distance: string;
229
+ sparse: boolean;
230
+ quantization: string;
231
+ hnsw_m: number | null;
232
+ hnsw_ef_construct: number | null;
233
+ }
234
+ export interface IngestionProfile {
235
+ id: UUID;
236
+ tenant_id: UUID;
237
+ name: string;
238
+ params: IngestionProfileParams;
239
+ fingerprint: string;
240
+ is_default: boolean;
241
+ }
242
+ /** Fields settable when creating/updating a profile. `params` may be partial on create
243
+ * (omitted knobs take the blessed default). The owning tenant is the key's — never in the body. */
244
+ export interface IngestionProfileWrite {
245
+ name?: string;
246
+ params?: Partial<IngestionProfileParams>;
247
+ is_default?: boolean;
214
248
  }
215
249
  export interface Folder {
216
250
  id: UUID;
@@ -342,16 +376,22 @@ export interface ChunkHeat {
342
376
  bbox: [number, number, number, number];
343
377
  }[] | null;
344
378
  }
345
- /** Everything to overlay retrieval heat on the original document: a signed view-PDF URL
346
- * (render with pdf.js), its per-page geometry, and every chunk's heat + boxes. */
379
+ /** Everything to overlay retrieval heat on the original document. Fetch the view-PDF BYTES
380
+ * via `analytics.documentViewPdf(id)` from YOUR BACKEND (KC serves them; the browser never
381
+ * touches GCS), render with pdf.js, and shade chunks by heat. `insufficient_data` (with
382
+ * `retrieval_events` as the sample size) is the document-level analogue of the corpus heat
383
+ * view's insufficient_data band — show a clear "not enough data yet" indication instead of
384
+ * misleading shading when it's true. */
347
385
  export interface ChunkHeatmapResponse {
348
386
  document_id: UUID;
349
- view_pdf_url: string | null;
387
+ has_view_pdf: boolean;
350
388
  page_dims: {
351
389
  w: number;
352
390
  h: number;
353
391
  rotation: number;
354
392
  }[] | null;
393
+ retrieval_events: number;
394
+ insufficient_data: boolean;
355
395
  chunks: ChunkHeat[];
356
396
  }
357
397
  export interface MonthlyCorpus {
@@ -563,6 +603,8 @@ declare class HttpBase {
563
603
  protected url(path: string, query?: RequestOpts["query"]): string;
564
604
  protected raw(method: string, path: string, opts?: RequestOpts): Promise<Response>;
565
605
  protected request<T>(method: string, path: string, opts?: RequestOpts): Promise<T>;
606
+ /** GET binary bytes as a Blob (error bodies are JSON, so decode+throw on !ok). */
607
+ protected rawBytes(method: string, path: string, signal?: AbortSignal): Promise<Blob>;
566
608
  /** Auto-paginate a list endpoint into a single array. */
567
609
  protected pageAll<T>(path: string, query?: RequestOpts["query"]): Promise<T[]>;
568
610
  }
@@ -673,6 +715,7 @@ export declare class KnowledgeCoreClient extends HttpBase {
673
715
  name: string;
674
716
  description?: string;
675
717
  custom_metadata?: Record<string, unknown>;
718
+ profile_id?: UUID;
676
719
  }) => Promise<Corpus>;
677
720
  list: (q?: {
678
721
  limit?: number;
@@ -686,11 +729,13 @@ export declare class KnowledgeCoreClient extends HttpBase {
686
729
  cursor?: string;
687
730
  }) => Promise<Page<Corpus>>;
688
731
  get: (id: UUID) => Promise<Corpus>;
689
- /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). */
732
+ /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). `profile_id`
733
+ * reassigns the ingestion profile (does NOT re-index; applies on next (re)ingest). */
690
734
  update: (id: UUID, b: {
691
735
  name?: string;
692
736
  description?: string;
693
737
  custom_metadata?: Record<string, unknown>;
738
+ profile_id?: UUID | null;
694
739
  }) => Promise<Corpus>;
695
740
  delete: (id: UUID, confirmName: string) => Promise<void>;
696
741
  listFolders: (id: UUID) => Promise<Folder[]>;
@@ -940,6 +985,25 @@ export declare class KnowledgeCoreClient extends HttpBase {
940
985
  * capabilities (`supports_reasoning`), and mode↔model dependencies. */
941
986
  modelOptions: () => Promise<ModelOptions>;
942
987
  };
988
+ /** Ingestion profiles — the build-side config object (counterpart to `agents`). A profile
989
+ * DEFINES an index (chunking + embedding + sparse + quant); its `fingerprint` is the physical
990
+ * collection identity. Attach one to a corpus via `corpora.create/update({ profile_id })`. */
991
+ profiles: {
992
+ /** List this tenant's ingestion profiles. */
993
+ list: (q?: {
994
+ limit?: number;
995
+ cursor?: string;
996
+ }) => Promise<Page<IngestionProfile>>;
997
+ listAll: () => Promise<IngestionProfile[]>;
998
+ get: (id: UUID) => Promise<IngestionProfile>;
999
+ /** Create a profile owned by this tenant. Omit `params` for the blessed profile (all defaults). */
1000
+ create: (b: IngestionProfileWrite & {
1001
+ name: string;
1002
+ }) => Promise<IngestionProfile>;
1003
+ update: (id: UUID, b: IngestionProfileWrite) => Promise<IngestionProfile>;
1004
+ /** Delete a profile. 409 if any corpus still references it. */
1005
+ delete: (id: UUID) => Promise<void>;
1006
+ };
943
1007
  analytics: {
944
1008
  /** Query-volume time series for a corpus (the denominator for everything). */
945
1009
  corpusUsage: (corpusId: UUID, q?: {
@@ -962,9 +1026,15 @@ export declare class KnowledgeCoreClient extends HttpBase {
962
1026
  document: (documentId: UUID) => Promise<DocumentAnalytics>;
963
1027
  /** Parent-section grain for a document (never exposes chunk ids). */
964
1028
  documentSections: (documentId: UUID) => Promise<SectionsResponse>;
965
- /** Per-chunk retrieval heat + position in the document's view-PDF, plus a signed
966
- * view-PDF URL — everything to overlay heat on the ORIGINAL document with pdf.js. */
1029
+ /** Per-chunk retrieval heat + position + `has_view_pdf` + the `insufficient_data`
1030
+ * indication — everything to overlay heat on the ORIGINAL document with pdf.js. Fetch the
1031
+ * view-PDF bytes with `documentViewPdf(id)` (below). */
967
1032
  documentChunks: (documentId: UUID) => Promise<ChunkHeatmapResponse>;
1033
+ /** Fetch the document's canonical view-PDF BYTES (the surface for the heat overlay).
1034
+ * Call this from YOUR BACKEND — KC serves the bytes, so the browser never fetches from
1035
+ * GCS (no bucket CORS). Returns a Blob (Node: `Buffer.from(await blob.arrayBuffer())`;
1036
+ * browser: `URL.createObjectURL(blob)`). Rejects 404 when `has_view_pdf` is false. */
1037
+ documentViewPdf: (documentId: UUID, signal?: AbortSignal) => Promise<Blob>;
968
1038
  /** Whole-corpus scatter payload: every document with x=retrieved_reach, y=conversion,
969
1039
  * bubble=chunk_count, and a server-computed quadrant. No pagination (`truncated` if capped). */
970
1040
  corpusScatter: (corpusId: UUID) => Promise<ScatterResponse>;
package/dist/index.js CHANGED
@@ -102,6 +102,15 @@ class HttpBase {
102
102
  throw new KnowledgeCoreError(res.status, parsed ?? text, path);
103
103
  return parsed;
104
104
  }
105
+ /** GET binary bytes as a Blob (error bodies are JSON, so decode+throw on !ok). */
106
+ async rawBytes(method, path, signal) {
107
+ const res = await this.raw(method, path, { signal });
108
+ if (!res.ok) {
109
+ const t = await res.text();
110
+ throw new KnowledgeCoreError(res.status, safeJson(t), path);
111
+ }
112
+ return await res.blob();
113
+ }
105
114
  /** Auto-paginate a list endpoint into a single array. */
106
115
  async pageAll(path, query = {}) {
107
116
  const out = [];
@@ -259,7 +268,8 @@ export class KnowledgeCoreClient extends HttpBase {
259
268
  /** Filter corpora by custom_metadata using the same metadata filter as documents. */
260
269
  search: (b) => this.request("POST", "/v1/corpora/search", { json: b }),
261
270
  get: (id) => this.request("GET", `/v1/corpora/${id}`),
262
- /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). */
271
+ /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). `profile_id`
272
+ * reassigns the ingestion profile (does NOT re-index; applies on next (re)ingest). */
263
273
  update: (id, b) => this.request("PATCH", `/v1/corpora/${id}`, { json: b }),
264
274
  delete: (id, confirmName) => this.request("DELETE", `/v1/corpora/${id}`, { query: { confirm: confirmName } }),
265
275
  listFolders: (id) => this.pageAll(`/v1/corpora/${id}/folders`),
@@ -458,6 +468,20 @@ export class KnowledgeCoreClient extends HttpBase {
458
468
  * capabilities (`supports_reasoning`), and mode↔model dependencies. */
459
469
  modelOptions: () => this.request("GET", "/v1/agents/model-options"),
460
470
  };
471
+ /** Ingestion profiles — the build-side config object (counterpart to `agents`). A profile
472
+ * DEFINES an index (chunking + embedding + sparse + quant); its `fingerprint` is the physical
473
+ * collection identity. Attach one to a corpus via `corpora.create/update({ profile_id })`. */
474
+ profiles = {
475
+ /** List this tenant's ingestion profiles. */
476
+ list: (q) => this.request("GET", "/v1/profiles", { query: q }),
477
+ listAll: () => this.pageAll("/v1/profiles"),
478
+ get: (id) => this.request("GET", `/v1/profiles/${id}`),
479
+ /** Create a profile owned by this tenant. Omit `params` for the blessed profile (all defaults). */
480
+ create: (b) => this.request("POST", "/v1/profiles", { json: b }),
481
+ update: (id, b) => this.request("PATCH", `/v1/profiles/${id}`, { json: b }),
482
+ /** Delete a profile. 409 if any corpus still references it. */
483
+ delete: (id) => this.request("DELETE", `/v1/profiles/${id}`),
484
+ };
461
485
  // --- retrieval analytics (read-only, tenant-scoped, aggregate-on-read) ---
462
486
  analytics = {
463
487
  /** Query-volume time series for a corpus (the denominator for everything). */
@@ -468,9 +492,15 @@ export class KnowledgeCoreClient extends HttpBase {
468
492
  document: (documentId) => this.request("GET", `/v1/analytics/documents/${documentId}`),
469
493
  /** Parent-section grain for a document (never exposes chunk ids). */
470
494
  documentSections: (documentId) => this.request("GET", `/v1/analytics/documents/${documentId}/sections`),
471
- /** Per-chunk retrieval heat + position in the document's view-PDF, plus a signed
472
- * view-PDF URL — everything to overlay heat on the ORIGINAL document with pdf.js. */
495
+ /** Per-chunk retrieval heat + position + `has_view_pdf` + the `insufficient_data`
496
+ * indication — everything to overlay heat on the ORIGINAL document with pdf.js. Fetch the
497
+ * view-PDF bytes with `documentViewPdf(id)` (below). */
473
498
  documentChunks: (documentId) => this.request("GET", `/v1/analytics/documents/${documentId}/chunks`),
499
+ /** Fetch the document's canonical view-PDF BYTES (the surface for the heat overlay).
500
+ * Call this from YOUR BACKEND — KC serves the bytes, so the browser never fetches from
501
+ * GCS (no bucket CORS). Returns a Blob (Node: `Buffer.from(await blob.arrayBuffer())`;
502
+ * browser: `URL.createObjectURL(blob)`). Rejects 404 when `has_view_pdf` is false. */
503
+ documentViewPdf: (documentId, signal) => this.rawBytes("GET", `/v1/analytics/documents/${documentId}/view-pdf`, signal),
474
504
  /** Whole-corpus scatter payload: every document with x=retrieved_reach, y=conversion,
475
505
  * bubble=chunk_count, and a server-computed quadrant. No pagination (`truncated` if capped). */
476
506
  corpusScatter: (corpusId) => this.request("GET", `/v1/analytics/corpora/${corpusId}/scatter`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babav/knowledge-core-client",
3
- "version": "0.38.1",
3
+ "version": "0.40.0",
4
4
  "description": "TypeScript client for the Babav Knowledge Core API (Deno + Node 18+, zero deps). Includes the babav.visual grammar TYPES at the ./visual subpath (types only; all visual rendering is server-side).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -244,6 +244,43 @@ export interface Corpus {
244
244
  name: string;
245
245
  description: string | null;
246
246
  custom_metadata: Record<string, unknown>;
247
+ profile_id: UUID | null; // ingestion profile the corpus is indexed under (null => tenant's blessed default)
248
+ }
249
+
250
+ /** Build-time knob bundle that DEFINES an index. Defaults are today's blessed config, so a
251
+ * profile created with no params is the blessed profile. Change any knob => different vectors
252
+ * => a different physical collection. Embedding/indexing/chunking is SYSTEM-level, never a
253
+ * query knob — this is the build-side counterpart to an Agent, not an agent. */
254
+ export interface IngestionProfileParams {
255
+ chunker: string; // "hybrid"
256
+ chunk_child_max_tokens: number;
257
+ chunk_parent_max_tokens: number;
258
+ contextualize: boolean;
259
+ embedding_provider: string; // "voyage_context" | "voyage" | "gemini"
260
+ embedding_model: string;
261
+ embedding_dimensions: number;
262
+ distance: string; // "cosine" | "dot" | "euclid"
263
+ sparse: boolean;
264
+ quantization: string; // "int8" | "none"
265
+ hnsw_m: number | null; // null => Qdrant default
266
+ hnsw_ef_construct: number | null; // null => Qdrant default
267
+ }
268
+
269
+ export interface IngestionProfile {
270
+ id: UUID;
271
+ tenant_id: UUID; // owning tenant (every profile belongs to one; no globals)
272
+ name: string;
273
+ params: IngestionProfileParams;
274
+ fingerprint: string; // stable hash of params — the physical collection identity
275
+ is_default: boolean; // at most one blessed profile per tenant
276
+ }
277
+
278
+ /** Fields settable when creating/updating a profile. `params` may be partial on create
279
+ * (omitted knobs take the blessed default). The owning tenant is the key's — never in the body. */
280
+ export interface IngestionProfileWrite {
281
+ name?: string;
282
+ params?: Partial<IngestionProfileParams>;
283
+ is_default?: boolean;
247
284
  }
248
285
  export interface Folder {
249
286
  id: UUID;
@@ -346,12 +383,18 @@ export interface ChunkHeat {
346
383
  method: string | null;
347
384
  regions: { page: number; bbox: [number, number, number, number] }[] | null;
348
385
  }
349
- /** Everything to overlay retrieval heat on the original document: a signed view-PDF URL
350
- * (render with pdf.js), its per-page geometry, and every chunk's heat + boxes. */
386
+ /** Everything to overlay retrieval heat on the original document. Fetch the view-PDF BYTES
387
+ * via `analytics.documentViewPdf(id)` from YOUR BACKEND (KC serves them; the browser never
388
+ * touches GCS), render with pdf.js, and shade chunks by heat. `insufficient_data` (with
389
+ * `retrieval_events` as the sample size) is the document-level analogue of the corpus heat
390
+ * view's insufficient_data band — show a clear "not enough data yet" indication instead of
391
+ * misleading shading when it's true. */
351
392
  export interface ChunkHeatmapResponse {
352
393
  document_id: UUID;
353
- view_pdf_url: string | null;
394
+ has_view_pdf: boolean; // a view-PDF exists -> fetch bytes with analytics.documentViewPdf(id)
354
395
  page_dims: { w: number; h: number; rotation: number }[] | null;
396
+ retrieval_events: number; // distinct retrievals this doc appeared in (heat sample size)
397
+ insufficient_data: boolean; // too few retrievals to trust per-chunk heat yet
355
398
  chunks: ChunkHeat[];
356
399
  }
357
400
  export interface MonthlyCorpus {
@@ -589,6 +632,16 @@ class HttpBase {
589
632
  return parsed as T;
590
633
  }
591
634
 
635
+ /** GET binary bytes as a Blob (error bodies are JSON, so decode+throw on !ok). */
636
+ protected async rawBytes(method: string, path: string, signal?: AbortSignal): Promise<Blob> {
637
+ const res = await this.raw(method, path, { signal });
638
+ if (!res.ok) {
639
+ const t = await res.text();
640
+ throw new KnowledgeCoreError(res.status, safeJson(t), path);
641
+ }
642
+ return await res.blob();
643
+ }
644
+
592
645
  /** Auto-paginate a list endpoint into a single array. */
593
646
  protected async pageAll<T>(path: string, query: RequestOpts["query"] = {}): Promise<T[]> {
594
647
  const out: T[] = [];
@@ -803,7 +856,7 @@ export class KnowledgeCoreClient extends HttpBase {
803
856
 
804
857
  // --- corpora ---
805
858
  corpora = {
806
- create: (b: { name: string; description?: string; custom_metadata?: Record<string, unknown> }) =>
859
+ create: (b: { name: string; description?: string; custom_metadata?: Record<string, unknown>; profile_id?: UUID }) =>
807
860
  this.request<Corpus>("POST", "/v1/corpora", { json: b }),
808
861
  list: (q?: { limit?: number; cursor?: string }) => this.request<Page<Corpus>>("GET", "/v1/corpora", { query: q }),
809
862
  listAll: () => this.pageAll<Corpus>("/v1/corpora"),
@@ -811,8 +864,9 @@ export class KnowledgeCoreClient extends HttpBase {
811
864
  search: (b: { filter?: MetadataFilter; limit?: number; cursor?: string }) =>
812
865
  this.request<Page<Corpus>>("POST", "/v1/corpora/search", { json: b }),
813
866
  get: (id: UUID) => this.request<Corpus>("GET", `/v1/corpora/${id}`),
814
- /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). */
815
- update: (id: UUID, b: { name?: string; description?: string; custom_metadata?: Record<string, unknown> }) =>
867
+ /** `custom_metadata` merges (provided keys upsert, a null value deletes a key). `profile_id`
868
+ * reassigns the ingestion profile (does NOT re-index; applies on next (re)ingest). */
869
+ update: (id: UUID, b: { name?: string; description?: string; custom_metadata?: Record<string, unknown>; profile_id?: UUID | null }) =>
816
870
  this.request<Corpus>("PATCH", `/v1/corpora/${id}`, { json: b }),
817
871
  delete: (id: UUID, confirmName: string) => this.request<void>("DELETE", `/v1/corpora/${id}`, { query: { confirm: confirmName } }),
818
872
  listFolders: (id: UUID) => this.pageAll<Folder>(`/v1/corpora/${id}/folders`),
@@ -1076,6 +1130,24 @@ export class KnowledgeCoreClient extends HttpBase {
1076
1130
  modelOptions: () => this.request<ModelOptions>("GET", "/v1/agents/model-options"),
1077
1131
  };
1078
1132
 
1133
+ /** Ingestion profiles — the build-side config object (counterpart to `agents`). A profile
1134
+ * DEFINES an index (chunking + embedding + sparse + quant); its `fingerprint` is the physical
1135
+ * collection identity. Attach one to a corpus via `corpora.create/update({ profile_id })`. */
1136
+ profiles = {
1137
+ /** List this tenant's ingestion profiles. */
1138
+ list: (q?: { limit?: number; cursor?: string }) =>
1139
+ this.request<Page<IngestionProfile>>("GET", "/v1/profiles", { query: q }),
1140
+ listAll: () => this.pageAll<IngestionProfile>("/v1/profiles"),
1141
+ get: (id: UUID) => this.request<IngestionProfile>("GET", `/v1/profiles/${id}`),
1142
+ /** Create a profile owned by this tenant. Omit `params` for the blessed profile (all defaults). */
1143
+ create: (b: IngestionProfileWrite & { name: string }) =>
1144
+ this.request<IngestionProfile>("POST", "/v1/profiles", { json: b }),
1145
+ update: (id: UUID, b: IngestionProfileWrite) =>
1146
+ this.request<IngestionProfile>("PATCH", `/v1/profiles/${id}`, { json: b }),
1147
+ /** Delete a profile. 409 if any corpus still references it. */
1148
+ delete: (id: UUID) => this.request<void>("DELETE", `/v1/profiles/${id}`),
1149
+ };
1150
+
1079
1151
  // --- retrieval analytics (read-only, tenant-scoped, aggregate-on-read) ---
1080
1152
  analytics = {
1081
1153
  /** Query-volume time series for a corpus (the denominator for everything). */
@@ -1088,9 +1160,16 @@ export class KnowledgeCoreClient extends HttpBase {
1088
1160
  document: (documentId: UUID) => this.request<DocumentAnalytics>("GET", `/v1/analytics/documents/${documentId}`),
1089
1161
  /** Parent-section grain for a document (never exposes chunk ids). */
1090
1162
  documentSections: (documentId: UUID) => this.request<SectionsResponse>("GET", `/v1/analytics/documents/${documentId}/sections`),
1091
- /** Per-chunk retrieval heat + position in the document's view-PDF, plus a signed
1092
- * view-PDF URL — everything to overlay heat on the ORIGINAL document with pdf.js. */
1163
+ /** Per-chunk retrieval heat + position + `has_view_pdf` + the `insufficient_data`
1164
+ * indication — everything to overlay heat on the ORIGINAL document with pdf.js. Fetch the
1165
+ * view-PDF bytes with `documentViewPdf(id)` (below). */
1093
1166
  documentChunks: (documentId: UUID) => this.request<ChunkHeatmapResponse>("GET", `/v1/analytics/documents/${documentId}/chunks`),
1167
+ /** Fetch the document's canonical view-PDF BYTES (the surface for the heat overlay).
1168
+ * Call this from YOUR BACKEND — KC serves the bytes, so the browser never fetches from
1169
+ * GCS (no bucket CORS). Returns a Blob (Node: `Buffer.from(await blob.arrayBuffer())`;
1170
+ * browser: `URL.createObjectURL(blob)`). Rejects 404 when `has_view_pdf` is false. */
1171
+ documentViewPdf: (documentId: UUID, signal?: AbortSignal): Promise<Blob> =>
1172
+ this.rawBytes("GET", `/v1/analytics/documents/${documentId}/view-pdf`, signal),
1094
1173
  /** Whole-corpus scatter payload: every document with x=retrieved_reach, y=conversion,
1095
1174
  * bubble=chunk_count, and a server-computed quadrant. No pagination (`truncated` if capped). */
1096
1175
  corpusScatter: (corpusId: UUID) => this.request<ScatterResponse>("GET", `/v1/analytics/corpora/${corpusId}/scatter`),