@babav/knowledge-core-client 0.22.4 → 0.22.5

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
@@ -359,12 +359,41 @@ export interface StreamHandlers {
359
359
  /** Catch-all for any event (incl. unknown ones). */
360
360
  onEvent?: (event: string, data: unknown) => void;
361
361
  }
362
+ /** A live document status transition pushed over the ingestion-status SSE stream
363
+ * (documents.events / folders.events). Replaces polling GET /documents/count. */
364
+ export interface DocumentStatusEvent {
365
+ document_id: string;
366
+ corpus_id?: string | null;
367
+ folder_id?: string | null;
368
+ status: string;
369
+ filename?: string | null;
370
+ chunk_count?: number | null;
371
+ error?: string | null;
372
+ counts?: DocumentCount;
373
+ }
374
+ export interface DocumentEventsSnapshot {
375
+ documents: DocumentStatusEvent[];
376
+ counts: DocumentCount;
377
+ }
378
+ export interface DocumentEventHandlers {
379
+ /** Current state on connect (one query) — render the list from this. */
380
+ onSnapshot?: (s: DocumentEventsSnapshot) => void;
381
+ /** A single document's status changed (the moment it happened). Update that row + counts. */
382
+ onDocument?: (e: DocumentStatusEvent) => void;
383
+ /** Nothing is in-flight anymore — the stream is about to close. Authoritative final counts. */
384
+ onComplete?: (counts: DocumentCount) => void;
385
+ onError?: (err: unknown) => void;
386
+ }
362
387
  export declare class KnowledgeCoreClient extends HttpBase {
363
388
  /** @param opts.apiKey a TENANT key, supplied by the caller. */
364
389
  constructor(opts: ClientOptions);
365
390
  query(agentId: UUID, body: QueryRequest): Promise<QueryResponse>;
366
391
  /** Streaming query (SSE). Resolves when the stream ends. */
367
392
  queryStream(agentId: UUID, body: QueryRequest, handlers: StreamHandlers, signal?: AbortSignal): Promise<void>;
393
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
394
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
395
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
396
+ _streamDocEvents(path: string, handlers: DocumentEventHandlers, signal?: AbortSignal): Promise<void>;
368
397
  retrieve(body: {
369
398
  query: string;
370
399
  corpus_ids: UUID[];
@@ -466,6 +495,8 @@ export declare class KnowledgeCoreClient extends HttpBase {
466
495
  limit?: number;
467
496
  cursor?: string;
468
497
  }) => Promise<Page<Document>>;
498
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
499
+ events: (folderId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) => Promise<void>;
469
500
  };
470
501
  documents: {
471
502
  get: (id: UUID) => Promise<Document>;
@@ -478,6 +509,10 @@ export declare class KnowledgeCoreClient extends HttpBase {
478
509
  }) => Promise<Document>;
479
510
  contentUrl: (id: UUID, disposition?: "inline" | "attachment") => Promise<ContentUrl>;
480
511
  delete: (id: UUID) => Promise<void>;
512
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
513
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
514
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
515
+ events: (corpusId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) => Promise<void>;
481
516
  };
482
517
  conversations: {
483
518
  create: (b?: {
package/dist/index.js CHANGED
@@ -176,6 +176,38 @@ export class KnowledgeCoreClient extends HttpBase {
176
176
  if (buf.trim())
177
177
  dispatchSse(buf, handlers);
178
178
  }
179
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
180
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
181
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
182
+ async _streamDocEvents(path, handlers, signal) {
183
+ const res = await this.raw("GET", path, { signal });
184
+ if (!res.ok || !res.body) {
185
+ const t = await res.text();
186
+ const err = new KnowledgeCoreError(res.status, safeJson(t), path);
187
+ if (handlers.onError) {
188
+ handlers.onError(err);
189
+ return;
190
+ }
191
+ throw err;
192
+ }
193
+ const reader = res.body.getReader();
194
+ const decoder = new TextDecoder();
195
+ let buf = "";
196
+ for (;;) {
197
+ const { value, done } = await reader.read();
198
+ if (done)
199
+ break;
200
+ buf += decoder.decode(value, { stream: true });
201
+ let idx;
202
+ while ((idx = buf.indexOf("\n\n")) !== -1) {
203
+ const frame = buf.slice(0, idx);
204
+ buf = buf.slice(idx + 2);
205
+ dispatchDocEvent(frame, handlers);
206
+ }
207
+ }
208
+ if (buf.trim())
209
+ dispatchDocEvent(buf, handlers);
210
+ }
179
211
  // --- retrieve (cross-corpus primitive, no generation) ---
180
212
  retrieve(body) {
181
213
  return this.request("POST", "/v1/retrieve", { json: body });
@@ -255,6 +287,8 @@ export class KnowledgeCoreClient extends HttpBase {
255
287
  countDocuments: (folderId) => this.request("GET", `/v1/folders/${folderId}/documents/count`),
256
288
  /** Documents in the folder whose filename contains `q` (case-insensitive), paginated. */
257
289
  searchDocuments: (folderId, q, opts) => this.request("GET", `/v1/folders/${folderId}/documents/search`, { query: { q, ...opts } }),
290
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
291
+ events: (folderId, handlers, signal) => this._streamDocEvents(`/v1/folders/${folderId}/documents/events`, handlers, signal),
258
292
  };
259
293
  // --- documents ---
260
294
  documents = {
@@ -265,6 +299,10 @@ export class KnowledgeCoreClient extends HttpBase {
265
299
  update: (id, b) => this.request("PATCH", `/v1/documents/${id}`, { json: b }),
266
300
  contentUrl: (id, disposition = "inline") => this.request("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
267
301
  delete: (id) => this.request("DELETE", `/v1/documents/${id}`),
302
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
303
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
304
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
305
+ events: (corpusId, handlers, signal) => this._streamDocEvents(`/v1/corpora/${corpusId}/documents/events`, handlers, signal),
268
306
  };
269
307
  // --- conversations ---
270
308
  conversations = {
@@ -384,3 +422,27 @@ function dispatchSse(frame, h) {
384
422
  break;
385
423
  }
386
424
  }
425
+ function dispatchDocEvent(frame, h) {
426
+ let event = "message";
427
+ const dataLines = [];
428
+ for (const line of frame.split("\n")) {
429
+ if (line.startsWith("event:"))
430
+ event = line.slice(6).trim();
431
+ else if (line.startsWith("data:"))
432
+ dataLines.push(line.slice(5).trim());
433
+ }
434
+ if (dataLines.length === 0)
435
+ return; // heartbeat / comment frame
436
+ const data = safeJson(dataLines.join("\n"));
437
+ switch (event) {
438
+ case "snapshot":
439
+ h.onSnapshot?.(data);
440
+ break;
441
+ case "document":
442
+ h.onDocument?.(data);
443
+ break;
444
+ case "complete":
445
+ h.onComplete?.(data);
446
+ break;
447
+ }
448
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babav/knowledge-core-client",
3
- "version": "0.22.4",
3
+ "version": "0.22.5",
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
@@ -480,6 +480,32 @@ export interface StreamHandlers {
480
480
  onEvent?: (event: string, data: unknown) => void;
481
481
  }
482
482
 
483
+ /** A live document status transition pushed over the ingestion-status SSE stream
484
+ * (documents.events / folders.events). Replaces polling GET /documents/count. */
485
+ export interface DocumentStatusEvent {
486
+ document_id: string;
487
+ corpus_id?: string | null;
488
+ folder_id?: string | null;
489
+ status: string; // pending | ingesting | indexed | failed | deleting
490
+ filename?: string | null;
491
+ chunk_count?: number | null;
492
+ error?: string | null;
493
+ counts?: DocumentCount; // running aggregate, included on each `document` delta
494
+ }
495
+ export interface DocumentEventsSnapshot {
496
+ documents: DocumentStatusEvent[]; // current state of every doc in the corpus/folder
497
+ counts: DocumentCount;
498
+ }
499
+ export interface DocumentEventHandlers {
500
+ /** Current state on connect (one query) — render the list from this. */
501
+ onSnapshot?: (s: DocumentEventsSnapshot) => void;
502
+ /** A single document's status changed (the moment it happened). Update that row + counts. */
503
+ onDocument?: (e: DocumentStatusEvent) => void;
504
+ /** Nothing is in-flight anymore — the stream is about to close. Authoritative final counts. */
505
+ onComplete?: (counts: DocumentCount) => void;
506
+ onError?: (err: unknown) => void;
507
+ }
508
+
483
509
  // ---------------------------------------------------------------------------
484
510
  // Tenant client (data ops) — use a TENANT key
485
511
  // ---------------------------------------------------------------------------
@@ -518,6 +544,34 @@ export class KnowledgeCoreClient extends HttpBase {
518
544
  if (buf.trim()) dispatchSse(buf, handlers);
519
545
  }
520
546
 
547
+ /** Shared SSE reader for the document-status streams (documents.events / folders.events).
548
+ * Resolves when the stream ends (server sends `complete` once nothing is in-flight, or the
549
+ * cap is hit). Abort via the signal to stop watching. Throws 501 if the env has no bus. */
550
+ async _streamDocEvents(path: string, handlers: DocumentEventHandlers, signal?: AbortSignal): Promise<void> {
551
+ const res = await this.raw("GET", path, { signal });
552
+ if (!res.ok || !res.body) {
553
+ const t = await res.text();
554
+ const err = new KnowledgeCoreError(res.status, safeJson(t), path);
555
+ if (handlers.onError) { handlers.onError(err); return; }
556
+ throw err;
557
+ }
558
+ const reader = res.body.getReader();
559
+ const decoder = new TextDecoder();
560
+ let buf = "";
561
+ for (;;) {
562
+ const { value, done } = await reader.read();
563
+ if (done) break;
564
+ buf += decoder.decode(value, { stream: true });
565
+ let idx: number;
566
+ while ((idx = buf.indexOf("\n\n")) !== -1) {
567
+ const frame = buf.slice(0, idx);
568
+ buf = buf.slice(idx + 2);
569
+ dispatchDocEvent(frame, handlers);
570
+ }
571
+ }
572
+ if (buf.trim()) dispatchDocEvent(buf, handlers);
573
+ }
574
+
521
575
  // --- retrieve (cross-corpus primitive, no generation) ---
522
576
  retrieve(body: { query: string; corpus_ids: UUID[]; top_k_retrieved_chunks?: number; top_k_reranked_chunks?: number; rerank?: boolean; instruction?: string; filter?: MetadataFilter; }): Promise<{ retrieval_contents: RetrievalContent[] }> {
523
577
  return this.request("POST", "/v1/retrieve", { json: body });
@@ -607,6 +661,9 @@ export class KnowledgeCoreClient extends HttpBase {
607
661
  /** Documents in the folder whose filename contains `q` (case-insensitive), paginated. */
608
662
  searchDocuments: (folderId: UUID, q: string, opts?: { limit?: number; cursor?: string }) =>
609
663
  this.request<Page<Document>>("GET", `/v1/folders/${folderId}/documents/search`, { query: { q, ...opts } }),
664
+ /** Live ingestion-status stream for ONE folder (same contract as documents.events). */
665
+ events: (folderId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) =>
666
+ this._streamDocEvents(`/v1/folders/${folderId}/documents/events`, handlers, signal),
610
667
  };
611
668
 
612
669
  // --- documents ---
@@ -620,6 +677,11 @@ export class KnowledgeCoreClient extends HttpBase {
620
677
  contentUrl: (id: UUID, disposition: "inline" | "attachment" = "inline") =>
621
678
  this.request<ContentUrl>("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
622
679
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/documents/${id}`),
680
+ /** Live ingestion-status stream for a corpus (SSE PUSH — replaces polling count). Snapshot on
681
+ * connect, then a delta per transition, then `complete` when nothing is in-flight. Abort via
682
+ * the signal. 501 (onError) if the environment has no doc-status bus — fall back to polling. */
683
+ events: (corpusId: UUID, handlers: DocumentEventHandlers, signal?: AbortSignal) =>
684
+ this._streamDocEvents(`/v1/corpora/${corpusId}/documents/events`, handlers, signal),
623
685
  };
624
686
 
625
687
  // --- conversations ---
@@ -733,3 +795,19 @@ function dispatchSse(frame: string, h: StreamHandlers): void {
733
795
  case "done": h.onDone?.(); break;
734
796
  }
735
797
  }
798
+
799
+ function dispatchDocEvent(frame: string, h: DocumentEventHandlers): void {
800
+ let event = "message";
801
+ const dataLines: string[] = [];
802
+ for (const line of frame.split("\n")) {
803
+ if (line.startsWith("event:")) event = line.slice(6).trim();
804
+ else if (line.startsWith("data:")) dataLines.push(line.slice(5).trim());
805
+ }
806
+ if (dataLines.length === 0) return; // heartbeat / comment frame
807
+ const data = safeJson(dataLines.join("\n"));
808
+ switch (event) {
809
+ case "snapshot": h.onSnapshot?.(data as DocumentEventsSnapshot); break;
810
+ case "document": h.onDocument?.(data as DocumentStatusEvent); break;
811
+ case "complete": h.onComplete?.(data as DocumentCount); break;
812
+ }
813
+ }