@babav/knowledge-core-client 0.1.0 → 0.3.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
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Babav Knowledge Core — TypeScript client.
3
3
  *
4
- * Portable: uses the global `fetch` + `ReadableStream`, so it runs unchanged in
5
- * Supabase Edge Functions (Deno) and Railway (Node 18+). Zero dependencies.
4
+ * Portable: uses the global `fetch`, `FormData`, `Blob` + `ReadableStream`, so it
5
+ * runs unchanged in Supabase Edge Functions (Deno) and Railway (Node 18+). Zero
6
+ * dependencies.
6
7
  *
7
8
  * Wire shapes are snake_case to mirror the Knowledge Core API 1:1 — so an API
8
9
  * change is reflected here with a one-to-one type edit and never drifts.
9
10
  *
10
- * Auth: every call carries an X-API-Key. Use TenantClient with a TENANT key for
11
- * all data ops; use AdminClient with the ADMIN key for tenant / API-key / agent
12
- * management. The key is server-side only — never ship it to a browser.
11
+ * Auth: every call carries an X-API-Key. Use KnowledgeCoreClient with a TENANT
12
+ * key for all data ops; use AdminClient with the ADMIN key for tenant / API-key /
13
+ * agent management. The key is server-side only — never ship it to a browser.
13
14
  */
14
15
  export type UUID = string;
15
16
  export interface Page<T> {
@@ -18,6 +19,8 @@ export interface Page<T> {
18
19
  }
19
20
  export type Visibility = "downloadable" | "viewable" | "attributable" | "hidden";
20
21
  export type FilterOp = "eq" | "ne" | "in" | "nin" | "gt" | "gte" | "lt" | "lte" | "exists";
22
+ /** Raw file input accepted by upload/parse helpers. */
23
+ export type FileData = Blob | Uint8Array | ArrayBuffer;
21
24
  export interface FilterClause {
22
25
  field: string;
23
26
  op: FilterOp;
@@ -80,53 +83,45 @@ export interface Groundedness {
80
83
  }
81
84
  export interface Corpus {
82
85
  id: UUID;
83
- tenant_id: UUID | null;
84
86
  name: string;
85
87
  description: string | null;
86
- embedding_model: string;
87
- external_ref: string | null;
88
- created_at: string;
89
- updated_at: string;
90
88
  }
91
89
  export interface Folder {
92
90
  id: UUID;
93
91
  corpus_id: UUID;
94
92
  name: string;
95
- is_default: boolean;
96
- created_at: string;
97
- updated_at: string;
98
93
  }
99
94
  export interface Document {
100
95
  id: UUID;
101
96
  corpus_id: UUID;
102
97
  folder_id: UUID;
103
- source_uri: string;
104
98
  status: string;
105
99
  visibility: Visibility;
106
100
  content_type: string | null;
107
101
  filename: string | null;
108
- parse_job_id: UUID | null;
109
102
  chunk_count: number;
110
- datapoint_ids: string[];
111
103
  custom_metadata: Record<string, unknown>;
112
104
  error: string | null;
113
- created_at: string;
114
- updated_at: string;
115
105
  }
116
106
  export interface ContentUrl {
117
107
  content_url: string;
118
108
  content_type: string;
119
109
  filename: string;
120
110
  }
111
+ /** Document tally for a corpus or folder. `indexed` = searchable; `in_flight` =
112
+ * pending + ingesting (still processing); `failed` = terminal; `total` = all. */
113
+ export interface DocumentCount {
114
+ total: number;
115
+ indexed: number;
116
+ in_flight: number;
117
+ failed: number;
118
+ }
121
119
  export interface Conversation {
122
120
  id: UUID;
123
- tenant_id: UUID | null;
124
121
  title: string | null;
125
122
  custom_metadata: Record<string, unknown>;
126
123
  ephemeral: boolean;
127
124
  last_activity: string;
128
- created_at: string;
129
- updated_at: string;
130
125
  }
131
126
  export interface Message {
132
127
  id: UUID;
@@ -138,55 +133,33 @@ export interface Message {
138
133
  citations: Record<string, unknown> | null;
139
134
  groundedness: Record<string, unknown> | null;
140
135
  retrieval_contents: unknown[] | null;
141
- retrieval_debug: Record<string, unknown> | null;
142
136
  usage: Record<string, unknown> | null;
143
- created_at: string;
144
137
  }
145
- export interface ConversationDocument {
138
+ export interface Attachment {
146
139
  id: UUID;
147
140
  conversation_id: UUID;
148
- tenant_id: UUID;
149
141
  filename: string | null;
150
142
  content_type: string | null;
151
143
  byte_size: number | null;
152
144
  status: "parsing" | "ready" | "failed";
153
145
  custom_metadata: Record<string, unknown> | null;
154
146
  error: string | null;
155
- created_at: string;
156
- updated_at: string;
157
- }
158
- export interface ParseJob {
159
- id: UUID;
160
- tenant_id: UUID | null;
161
- status: string;
162
- document_count: number;
163
- completed_count: number;
164
- failed_count: number;
165
- documents: unknown[];
166
- created_at: string;
167
- updated_at: string;
168
147
  }
169
148
  export interface Feedback {
170
149
  id: UUID;
171
150
  message_id: UUID;
172
151
  rating: number | null;
173
152
  comment: string | null;
174
- created_at: string;
175
- updated_at: string;
176
153
  }
177
154
  export interface Agent {
178
155
  id: UUID;
179
156
  name: string;
180
- kind: "query" | "ingestion";
181
- tenant_id: UUID | null;
182
157
  generation_model: string | null;
183
158
  top_k: number | null;
184
159
  rerank_instruction: string | null;
185
160
  max_hops: number | null;
186
161
  groundedness_threshold: number | null;
187
162
  grounding_enabled: boolean | null;
188
- created_at: string;
189
- updated_at: string;
190
163
  }
191
164
  export interface Tenant {
192
165
  id: UUID;
@@ -198,25 +171,18 @@ export interface Tenant {
198
171
  query_endpoint: string | null;
199
172
  query_public_domain: string | null;
200
173
  query_deployed_index_id: string | null;
201
- created_at: string;
202
- updated_at: string;
203
174
  }
204
175
  export interface ApiKeyCreated {
205
176
  id: UUID;
206
177
  api_key: string;
207
178
  prefix: string;
208
179
  label: string | null;
209
- created_at: string;
210
180
  }
211
- export interface ApiKeyInfo {
181
+ export interface ApiKey {
212
182
  id: UUID;
213
- tenant_id: UUID;
214
183
  prefix: string;
215
184
  label: string | null;
216
- last_used_at: string | null;
217
- revoked_at: string | null;
218
- created_at: string;
219
- updated_at: string;
185
+ revoked: boolean;
220
186
  }
221
187
  /** Thrown on any non-2xx response. `detail` is the structured body when present
222
188
  * (e.g. {error:"attachments_pending", attachments:[...]} or
@@ -232,7 +198,8 @@ export declare class KnowledgeCoreError extends Error {
232
198
  export interface ClientOptions {
233
199
  baseUrl: string;
234
200
  apiKey: string;
235
- /** Optional default fetch timeout (ms). Streaming ignores this. */
201
+ /** Optional default fetch timeout (ms). Streaming ignores this. Note:
202
+ * synchronous ingest can take a while — set this generously or omit. */
236
203
  timeoutMs?: number;
237
204
  fetch?: typeof fetch;
238
205
  }
@@ -289,9 +256,7 @@ export declare class KnowledgeCoreClient extends HttpBase {
289
256
  corpora: {
290
257
  create: (b: {
291
258
  name: string;
292
- embedding_model: string;
293
259
  description?: string;
294
- external_ref?: string;
295
260
  }) => Promise<Corpus>;
296
261
  list: (q?: {
297
262
  limit?: number;
@@ -302,7 +267,6 @@ export declare class KnowledgeCoreClient extends HttpBase {
302
267
  update: (id: UUID, b: {
303
268
  name?: string;
304
269
  description?: string;
305
- external_ref?: string;
306
270
  }) => Promise<Corpus>;
307
271
  delete: (id: UUID, confirmName: string) => Promise<void>;
308
272
  listFolders: (id: UUID) => Promise<Folder[]>;
@@ -310,26 +274,27 @@ export declare class KnowledgeCoreClient extends HttpBase {
310
274
  limit?: number;
311
275
  cursor?: string;
312
276
  }) => Promise<Page<Document>>;
313
- createDocument: (id: UUID, b: {
314
- source_uri: string;
315
- folder_id?: UUID;
277
+ /** Count documents in the corpus: { total, indexed, in_flight, failed }. */
278
+ countDocuments: (id: UUID) => Promise<DocumentCount>;
279
+ /** Upload a file and ingest it ASYNCHRONOUSLY — resolves with the created
280
+ * document at status `pending` (HTTP 202); track via documents.get() polling
281
+ * the status to `indexed`/`failed`, or the tenant result webhook. */
282
+ ingestDocument: (id: UUID, a: {
283
+ file: FileData;
284
+ filename: string;
285
+ content_type?: string;
316
286
  visibility?: Visibility;
317
- status?: string;
318
- parse_job_id?: UUID;
287
+ folder_id?: UUID;
319
288
  custom_metadata?: Record<string, unknown>;
320
289
  }) => Promise<Document>;
321
- ingest: (id: UUID, b: {
322
- ingestion_agent_id: UUID;
323
- folder_id?: UUID;
324
- documents: {
325
- gcs_uri: string;
326
- custom_metadata?: Record<string, unknown>;
327
- visibility?: Visibility;
328
- }[];
329
- }) => Promise<{
330
- parse_job_id: UUID;
331
- }>;
332
290
  };
291
+ parse(a: {
292
+ file: FileData;
293
+ filename?: string;
294
+ content_type?: string;
295
+ }): Promise<{
296
+ text: string;
297
+ }>;
333
298
  folders: {
334
299
  create: (corpusId: UUID, name: string) => Promise<Folder>;
335
300
  rename: (folderId: UUID, name: string) => Promise<Folder>;
@@ -338,17 +303,14 @@ export declare class KnowledgeCoreClient extends HttpBase {
338
303
  limit?: number;
339
304
  cursor?: string;
340
305
  }) => Promise<Page<Document>>;
306
+ /** Count documents in the folder: { total, indexed, in_flight, failed }. */
307
+ countDocuments: (folderId: UUID) => Promise<DocumentCount>;
341
308
  };
342
309
  documents: {
343
310
  get: (id: UUID) => Promise<Document>;
344
- getMetadata: (id: UUID) => Promise<{
345
- document_id: UUID;
346
- custom_metadata: Record<string, unknown>;
347
- }>;
348
- patchMetadata: (id: UUID, custom_metadata: Record<string, unknown>) => Promise<{
349
- document_id: UUID;
350
- custom_metadata: Record<string, unknown>;
351
- }>;
311
+ getCustomMetadata: (id: UUID) => Promise<Record<string, unknown>>;
312
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
313
+ patchCustomMetadata: (id: UUID, custom_metadata: Record<string, unknown>) => Promise<Record<string, unknown>>;
352
314
  update: (id: UUID, b: {
353
315
  visibility?: Visibility;
354
316
  folder_id?: UUID;
@@ -384,25 +346,20 @@ export declare class KnowledgeCoreClient extends HttpBase {
384
346
  }) => Promise<Page<Message>>;
385
347
  };
386
348
  attachments: {
387
- /** Upload + attach a document. `data` is the raw file bytes. Returns status=parsing. */
349
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
388
350
  upload: (conversationId: UUID, a: {
351
+ file: FileData;
389
352
  filename: string;
390
- content_type: string;
391
- data: BodyInit;
353
+ content_type?: string;
392
354
  custom_metadata?: Record<string, unknown>;
393
- }) => Promise<ConversationDocument>;
355
+ }) => Promise<Attachment>;
394
356
  list: (conversationId: UUID, q?: {
395
357
  limit?: number;
396
358
  cursor?: string;
397
- }) => Promise<Page<ConversationDocument>>;
398
- get: (conversationId: UUID, docId: UUID) => Promise<ConversationDocument>;
359
+ }) => Promise<Page<Attachment>>;
360
+ get: (conversationId: UUID, docId: UUID) => Promise<Attachment>;
399
361
  contentUrl: (conversationId: UUID, docId: UUID, disposition?: "inline" | "attachment") => Promise<ContentUrl>;
400
362
  delete: (conversationId: UUID, docId: UUID) => Promise<void>;
401
- /** Poll until the attachment is ready or failed (or timeout). */
402
- waitReady: (conversationId: UUID, docId: UUID, opts?: {
403
- intervalMs?: number;
404
- timeoutMs?: number;
405
- }) => Promise<ConversationDocument>;
406
363
  };
407
364
  messages: {
408
365
  get: (id: UUID) => Promise<Message>;
@@ -419,17 +376,8 @@ export declare class KnowledgeCoreClient extends HttpBase {
419
376
  get: (id: UUID) => Promise<Feedback>;
420
377
  delete: (id: UUID) => Promise<void>;
421
378
  };
422
- parseJobs: {
423
- list: (q?: {
424
- status_filter?: string;
425
- limit?: number;
426
- cursor?: string;
427
- }) => Promise<Page<ParseJob>>;
428
- get: (id: UUID) => Promise<ParseJob>;
429
- };
430
379
  agents: {
431
380
  list: (q?: {
432
- kind?: "query" | "ingestion";
433
381
  limit?: number;
434
382
  cursor?: string;
435
383
  }) => Promise<Page<Agent>>;
@@ -454,15 +402,12 @@ export declare class AdminClient extends HttpBase {
454
402
  listApiKeys: (tenantId: UUID, q?: {
455
403
  limit?: number;
456
404
  cursor?: string;
457
- }) => Promise<Page<ApiKeyInfo>>;
405
+ }) => Promise<Page<ApiKey>>;
458
406
  revokeApiKey: (tenantId: UUID, keyId: UUID) => Promise<void>;
459
407
  };
460
408
  agents: {
461
- /** tenant_id explicit, or null for a global/shared agent. */
462
409
  create: (b: {
463
410
  name: string;
464
- kind: "query" | "ingestion";
465
- tenant_id?: UUID | null;
466
411
  generation_model?: string;
467
412
  top_k?: number;
468
413
  rerank_instruction?: string;
@@ -470,7 +415,7 @@ export declare class AdminClient extends HttpBase {
470
415
  groundedness_threshold?: number;
471
416
  grounding_enabled?: boolean;
472
417
  }) => Promise<Agent>;
473
- update: (id: UUID, b: Partial<Omit<Agent, "id" | "kind" | "tenant_id" | "created_at" | "updated_at">>) => Promise<Agent>;
418
+ update: (id: UUID, b: Partial<Omit<Agent, "id">>) => Promise<Agent>;
474
419
  delete: (id: UUID) => Promise<void>;
475
420
  };
476
421
  }
package/dist/index.js CHANGED
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * Babav Knowledge Core — TypeScript client.
3
3
  *
4
- * Portable: uses the global `fetch` + `ReadableStream`, so it runs unchanged in
5
- * Supabase Edge Functions (Deno) and Railway (Node 18+). Zero dependencies.
4
+ * Portable: uses the global `fetch`, `FormData`, `Blob` + `ReadableStream`, so it
5
+ * runs unchanged in Supabase Edge Functions (Deno) and Railway (Node 18+). Zero
6
+ * dependencies.
6
7
  *
7
8
  * Wire shapes are snake_case to mirror the Knowledge Core API 1:1 — so an API
8
9
  * change is reflected here with a one-to-one type edit and never drifts.
9
10
  *
10
- * Auth: every call carries an X-API-Key. Use TenantClient with a TENANT key for
11
- * all data ops; use AdminClient with the ADMIN key for tenant / API-key / agent
12
- * management. The key is server-side only — never ship it to a browser.
11
+ * Auth: every call carries an X-API-Key. Use KnowledgeCoreClient with a TENANT
12
+ * key for all data ops; use AdminClient with the ADMIN key for tenant / API-key /
13
+ * agent management. The key is server-side only — never ship it to a browser.
13
14
  */
14
15
  // ---------------------------------------------------------------------------
15
16
  // Errors
@@ -63,6 +64,7 @@ class HttpBase {
63
64
  body = JSON.stringify(opts.json);
64
65
  }
65
66
  else if (opts.body !== undefined) {
67
+ // FormData sets its own multipart content-type (with boundary) on fetch.
66
68
  body = opts.body;
67
69
  }
68
70
  let signal = opts.signal;
@@ -110,6 +112,11 @@ function safeJson(t) {
110
112
  return t;
111
113
  }
112
114
  }
115
+ function toBlob(data, contentType) {
116
+ if (data instanceof Blob)
117
+ return data;
118
+ return new Blob([data], { type: contentType ?? "application/octet-stream" });
119
+ }
113
120
  // ---------------------------------------------------------------------------
114
121
  // Tenant client (data ops) — use a TENANT key
115
122
  // ---------------------------------------------------------------------------
@@ -157,21 +164,44 @@ export class KnowledgeCoreClient extends HttpBase {
157
164
  delete: (id, confirmName) => this.request("DELETE", `/v1/corpora/${id}`, { query: { confirm: confirmName } }),
158
165
  listFolders: (id) => this.pageAll(`/v1/corpora/${id}/folders`),
159
166
  listDocuments: (id, q) => this.request("GET", `/v1/corpora/${id}/documents`, { query: q }),
160
- createDocument: (id, b) => this.request("POST", `/v1/corpora/${id}/documents`, { json: b }),
161
- ingest: (id, b) => this.request("POST", `/v1/corpora/${id}/ingest`, { json: b }),
167
+ /** Count documents in the corpus: { total, indexed, in_flight, failed }. */
168
+ countDocuments: (id) => this.request("GET", `/v1/corpora/${id}/documents/count`),
169
+ /** Upload a file and ingest it ASYNCHRONOUSLY — resolves with the created
170
+ * document at status `pending` (HTTP 202); track via documents.get() polling
171
+ * the status to `indexed`/`failed`, or the tenant result webhook. */
172
+ ingestDocument: (id, a) => {
173
+ const fd = new FormData();
174
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
175
+ if (a.custom_metadata)
176
+ fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
177
+ if (a.visibility)
178
+ fd.append("visibility", a.visibility);
179
+ if (a.folder_id)
180
+ fd.append("folder_id", a.folder_id);
181
+ return this.request("POST", `/v1/corpora/${id}/documents`, { body: fd });
182
+ },
162
183
  };
184
+ // --- parsing (utility: file -> text, stores nothing) ---
185
+ parse(a) {
186
+ const fd = new FormData();
187
+ fd.append("file", toBlob(a.file, a.content_type), a.filename ?? "document");
188
+ return this.request("POST", "/v1/documents/parse", { body: fd });
189
+ }
163
190
  // --- folders ---
164
191
  folders = {
165
192
  create: (corpusId, name) => this.request("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
166
193
  rename: (folderId, name) => this.request("PATCH", `/v1/folders/${folderId}`, { json: { name } }),
167
194
  delete: (folderId) => this.request("DELETE", `/v1/folders/${folderId}`),
168
195
  listDocuments: (folderId, q) => this.request("GET", `/v1/folders/${folderId}/documents`, { query: q }),
196
+ /** Count documents in the folder: { total, indexed, in_flight, failed }. */
197
+ countDocuments: (folderId) => this.request("GET", `/v1/folders/${folderId}/documents/count`),
169
198
  };
170
199
  // --- documents ---
171
200
  documents = {
172
201
  get: (id) => this.request("GET", `/v1/documents/${id}`),
173
- getMetadata: (id) => this.request("GET", `/v1/documents/${id}/metadata`),
174
- patchMetadata: (id, custom_metadata) => this.request("PATCH", `/v1/documents/${id}/metadata`, { json: { custom_metadata } }),
202
+ getCustomMetadata: (id) => this.request("GET", `/v1/documents/${id}/custom_metadata`),
203
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
204
+ patchCustomMetadata: (id, custom_metadata) => this.request("PATCH", `/v1/documents/${id}/custom_metadata`, { json: custom_metadata }),
175
205
  update: (id, b) => this.request("PATCH", `/v1/documents/${id}`, { json: b }),
176
206
  contentUrl: (id, disposition = "inline") => this.request("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
177
207
  delete: (id) => this.request("DELETE", `/v1/documents/${id}`),
@@ -189,27 +219,18 @@ export class KnowledgeCoreClient extends HttpBase {
189
219
  };
190
220
  // --- conversation attachments (pinned full-document review) ---
191
221
  attachments = {
192
- /** Upload + attach a document. `data` is the raw file bytes. Returns status=parsing. */
193
- upload: (conversationId, a) => this.request("POST", `/v1/conversations/${conversationId}/documents`, {
194
- body: a.data,
195
- headers: { "content-type": a.content_type },
196
- query: { filename: a.filename, content_type: a.content_type, custom_metadata: a.custom_metadata ? JSON.stringify(a.custom_metadata) : undefined },
197
- }),
222
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
223
+ upload: (conversationId, a) => {
224
+ const fd = new FormData();
225
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
226
+ if (a.custom_metadata)
227
+ fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
228
+ return this.request("POST", `/v1/conversations/${conversationId}/documents`, { body: fd });
229
+ },
198
230
  list: (conversationId, q) => this.request("GET", `/v1/conversations/${conversationId}/documents`, { query: q }),
199
231
  get: (conversationId, docId) => this.request("GET", `/v1/conversations/${conversationId}/documents/${docId}`),
200
232
  contentUrl: (conversationId, docId, disposition = "inline") => this.request("GET", `/v1/conversations/${conversationId}/documents/${docId}/content-url`, { query: { disposition } }),
201
233
  delete: (conversationId, docId) => this.request("DELETE", `/v1/conversations/${conversationId}/documents/${docId}`),
202
- /** Poll until the attachment is ready or failed (or timeout). */
203
- waitReady: async (conversationId, docId, opts = {}) => {
204
- const interval = opts.intervalMs ?? 3000;
205
- const deadline = Date.now() + (opts.timeoutMs ?? 180_000);
206
- for (;;) {
207
- const d = await this.attachments.get(conversationId, docId);
208
- if (d.status === "ready" || d.status === "failed" || Date.now() > deadline)
209
- return d;
210
- await sleep(interval);
211
- }
212
- },
213
234
  };
214
235
  // --- messages + feedback ---
215
236
  messages = {
@@ -221,11 +242,6 @@ export class KnowledgeCoreClient extends HttpBase {
221
242
  get: (id) => this.request("GET", `/v1/feedback/${id}`),
222
243
  delete: (id) => this.request("DELETE", `/v1/feedback/${id}`),
223
244
  };
224
- // --- parse jobs ---
225
- parseJobs = {
226
- list: (q) => this.request("GET", "/v1/parse_jobs", { query: q }),
227
- get: (id) => this.request("GET", `/v1/parse_jobs/${id}`),
228
- };
229
245
  // --- agents (tenant read-only: choose an agent to query) ---
230
246
  agents = {
231
247
  list: (q) => this.request("GET", "/v1/agents", { query: q }),
@@ -248,8 +264,8 @@ export class AdminClient extends HttpBase {
248
264
  listApiKeys: (tenantId, q) => this.request("GET", `/v1/tenants/${tenantId}/api-keys`, { query: q }),
249
265
  revokeApiKey: (tenantId, keyId) => this.request("DELETE", `/v1/tenants/${tenantId}/api-keys/${keyId}`),
250
266
  };
267
+ // Agents are query agents, global for now.
251
268
  agents = {
252
- /** tenant_id explicit, or null for a global/shared agent. */
253
269
  create: (b) => this.request("POST", "/v1/agents", { json: b }),
254
270
  update: (id, b) => this.request("PATCH", `/v1/agents/${id}`, { json: b }),
255
271
  delete: (id) => this.request("DELETE", `/v1/agents/${id}`),
@@ -292,6 +308,3 @@ function dispatchSse(frame, h) {
292
308
  break;
293
309
  }
294
310
  }
295
- function sleep(ms) {
296
- return new Promise((r) => setTimeout(r, ms));
297
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babav/knowledge-core-client",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "TypeScript client for the Babav Knowledge Core API (Deno + Node 18+, zero deps).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -1,19 +1,20 @@
1
1
  /**
2
2
  * Babav Knowledge Core — TypeScript client.
3
3
  *
4
- * Portable: uses the global `fetch` + `ReadableStream`, so it runs unchanged in
5
- * Supabase Edge Functions (Deno) and Railway (Node 18+). Zero dependencies.
4
+ * Portable: uses the global `fetch`, `FormData`, `Blob` + `ReadableStream`, so it
5
+ * runs unchanged in Supabase Edge Functions (Deno) and Railway (Node 18+). Zero
6
+ * dependencies.
6
7
  *
7
8
  * Wire shapes are snake_case to mirror the Knowledge Core API 1:1 — so an API
8
9
  * change is reflected here with a one-to-one type edit and never drifts.
9
10
  *
10
- * Auth: every call carries an X-API-Key. Use TenantClient with a TENANT key for
11
- * all data ops; use AdminClient with the ADMIN key for tenant / API-key / agent
12
- * management. The key is server-side only — never ship it to a browser.
11
+ * Auth: every call carries an X-API-Key. Use KnowledgeCoreClient with a TENANT
12
+ * key for all data ops; use AdminClient with the ADMIN key for tenant / API-key /
13
+ * agent management. The key is server-side only — never ship it to a browser.
13
14
  */
14
15
 
15
16
  // ---------------------------------------------------------------------------
16
- // Shared types (mirror the API Read models)
17
+ // Shared types (mirror the API objects)
17
18
  // ---------------------------------------------------------------------------
18
19
  export type UUID = string;
19
20
 
@@ -25,6 +26,9 @@ export interface Page<T> {
25
26
  export type Visibility = "downloadable" | "viewable" | "attributable" | "hidden";
26
27
  export type FilterOp = "eq" | "ne" | "in" | "nin" | "gt" | "gte" | "lt" | "lte" | "exists";
27
28
 
29
+ /** Raw file input accepted by upload/parse helpers. */
30
+ export type FileData = Blob | Uint8Array | ArrayBuffer;
31
+
28
32
  export interface FilterClause {
29
33
  field: string;
30
34
  op: FilterOp;
@@ -93,53 +97,45 @@ export interface Groundedness {
93
97
 
94
98
  export interface Corpus {
95
99
  id: UUID;
96
- tenant_id: UUID | null;
97
100
  name: string;
98
101
  description: string | null;
99
- embedding_model: string;
100
- external_ref: string | null;
101
- created_at: string;
102
- updated_at: string;
103
102
  }
104
103
  export interface Folder {
105
104
  id: UUID;
106
105
  corpus_id: UUID;
107
106
  name: string;
108
- is_default: boolean;
109
- created_at: string;
110
- updated_at: string;
111
107
  }
112
108
  export interface Document {
113
109
  id: UUID;
114
110
  corpus_id: UUID;
115
111
  folder_id: UUID;
116
- source_uri: string;
117
112
  status: string;
118
113
  visibility: Visibility;
119
114
  content_type: string | null;
120
115
  filename: string | null;
121
- parse_job_id: UUID | null;
122
116
  chunk_count: number;
123
- datapoint_ids: string[];
124
117
  custom_metadata: Record<string, unknown>;
125
118
  error: string | null;
126
- created_at: string;
127
- updated_at: string;
128
119
  }
129
120
  export interface ContentUrl {
130
121
  content_url: string;
131
122
  content_type: string;
132
123
  filename: string;
133
124
  }
125
+ /** Document tally for a corpus or folder. `indexed` = searchable; `in_flight` =
126
+ * pending + ingesting (still processing); `failed` = terminal; `total` = all. */
127
+ export interface DocumentCount {
128
+ total: number;
129
+ indexed: number;
130
+ in_flight: number;
131
+ failed: number;
132
+ }
134
133
  export interface Conversation {
135
134
  id: UUID;
136
- tenant_id: UUID | null;
137
135
  title: string | null;
138
136
  custom_metadata: Record<string, unknown>;
139
137
  ephemeral: boolean;
140
138
  last_activity: string;
141
- created_at: string;
142
- updated_at: string;
143
139
  }
144
140
  export interface Message {
145
141
  id: UUID;
@@ -151,55 +147,33 @@ export interface Message {
151
147
  citations: Record<string, unknown> | null;
152
148
  groundedness: Record<string, unknown> | null;
153
149
  retrieval_contents: unknown[] | null;
154
- retrieval_debug: Record<string, unknown> | null;
155
150
  usage: Record<string, unknown> | null;
156
- created_at: string;
157
151
  }
158
- export interface ConversationDocument {
152
+ export interface Attachment {
159
153
  id: UUID;
160
154
  conversation_id: UUID;
161
- tenant_id: UUID;
162
155
  filename: string | null;
163
156
  content_type: string | null;
164
157
  byte_size: number | null;
165
158
  status: "parsing" | "ready" | "failed";
166
159
  custom_metadata: Record<string, unknown> | null;
167
160
  error: string | null;
168
- created_at: string;
169
- updated_at: string;
170
- }
171
- export interface ParseJob {
172
- id: UUID;
173
- tenant_id: UUID | null;
174
- status: string;
175
- document_count: number;
176
- completed_count: number;
177
- failed_count: number;
178
- documents: unknown[];
179
- created_at: string;
180
- updated_at: string;
181
161
  }
182
162
  export interface Feedback {
183
163
  id: UUID;
184
164
  message_id: UUID;
185
165
  rating: number | null;
186
166
  comment: string | null;
187
- created_at: string;
188
- updated_at: string;
189
167
  }
190
168
  export interface Agent {
191
169
  id: UUID;
192
170
  name: string;
193
- kind: "query" | "ingestion";
194
- tenant_id: UUID | null;
195
171
  generation_model: string | null;
196
172
  top_k: number | null;
197
173
  rerank_instruction: string | null;
198
174
  max_hops: number | null;
199
175
  groundedness_threshold: number | null;
200
176
  grounding_enabled: boolean | null;
201
- created_at: string;
202
- updated_at: string;
203
177
  }
204
178
  export interface Tenant {
205
179
  id: UUID;
@@ -211,25 +185,18 @@ export interface Tenant {
211
185
  query_endpoint: string | null;
212
186
  query_public_domain: string | null;
213
187
  query_deployed_index_id: string | null;
214
- created_at: string;
215
- updated_at: string;
216
188
  }
217
189
  export interface ApiKeyCreated {
218
190
  id: UUID;
219
191
  api_key: string; // shown ONCE — store it now
220
192
  prefix: string;
221
193
  label: string | null;
222
- created_at: string;
223
194
  }
224
- export interface ApiKeyInfo {
195
+ export interface ApiKey {
225
196
  id: UUID;
226
- tenant_id: UUID;
227
197
  prefix: string;
228
198
  label: string | null;
229
- last_used_at: string | null;
230
- revoked_at: string | null;
231
- created_at: string;
232
- updated_at: string;
199
+ revoked: boolean;
233
200
  }
234
201
 
235
202
  // ---------------------------------------------------------------------------
@@ -260,7 +227,8 @@ export class KnowledgeCoreError extends Error {
260
227
  export interface ClientOptions {
261
228
  baseUrl: string;
262
229
  apiKey: string;
263
- /** Optional default fetch timeout (ms). Streaming ignores this. */
230
+ /** Optional default fetch timeout (ms). Streaming ignores this. Note:
231
+ * synchronous ingest can take a while — set this generously or omit. */
264
232
  timeoutMs?: number;
265
233
  fetch?: typeof fetch; // override for tests
266
234
  }
@@ -268,7 +236,7 @@ export interface ClientOptions {
268
236
  interface RequestOpts {
269
237
  query?: Record<string, string | number | boolean | undefined>;
270
238
  json?: unknown;
271
- body?: BodyInit; // raw body (attachment upload)
239
+ body?: BodyInit; // raw body / multipart FormData (file uploads)
272
240
  headers?: Record<string, string>;
273
241
  signal?: AbortSignal;
274
242
  }
@@ -303,6 +271,7 @@ class HttpBase {
303
271
  headers["content-type"] = "application/json";
304
272
  body = JSON.stringify(opts.json);
305
273
  } else if (opts.body !== undefined) {
274
+ // FormData sets its own multipart content-type (with boundary) on fetch.
306
275
  body = opts.body;
307
276
  }
308
277
  let signal = opts.signal;
@@ -350,6 +319,11 @@ function safeJson(t: string): unknown {
350
319
  }
351
320
  }
352
321
 
322
+ function toBlob(data: FileData, contentType?: string): Blob {
323
+ if (data instanceof Blob) return data;
324
+ return new Blob([data as BlobPart], { type: contentType ?? "application/octet-stream" });
325
+ }
326
+
353
327
  // ---------------------------------------------------------------------------
354
328
  // SSE handlers
355
329
  // ---------------------------------------------------------------------------
@@ -404,23 +378,40 @@ export class KnowledgeCoreClient extends HttpBase {
404
378
 
405
379
  // --- corpora ---
406
380
  corpora = {
407
- create: (b: { name: string; embedding_model: string; description?: string; external_ref?: string }) =>
381
+ create: (b: { name: string; description?: string }) =>
408
382
  this.request<Corpus>("POST", "/v1/corpora", { json: b }),
409
383
  list: (q?: { limit?: number; cursor?: string }) => this.request<Page<Corpus>>("GET", "/v1/corpora", { query: q }),
410
384
  listAll: () => this.pageAll<Corpus>("/v1/corpora"),
411
385
  get: (id: UUID) => this.request<Corpus>("GET", `/v1/corpora/${id}`),
412
- update: (id: UUID, b: { name?: string; description?: string; external_ref?: string }) =>
386
+ update: (id: UUID, b: { name?: string; description?: string }) =>
413
387
  this.request<Corpus>("PATCH", `/v1/corpora/${id}`, { json: b }),
414
388
  delete: (id: UUID, confirmName: string) => this.request<void>("DELETE", `/v1/corpora/${id}`, { query: { confirm: confirmName } }),
415
389
  listFolders: (id: UUID) => this.pageAll<Folder>(`/v1/corpora/${id}/folders`),
416
390
  listDocuments: (id: UUID, q?: { limit?: number; cursor?: string }) =>
417
391
  this.request<Page<Document>>("GET", `/v1/corpora/${id}/documents`, { query: q }),
418
- createDocument: (id: UUID, b: { source_uri: string; folder_id?: UUID; visibility?: Visibility; status?: string; parse_job_id?: UUID; custom_metadata?: Record<string, unknown> }) =>
419
- this.request<Document>("POST", `/v1/corpora/${id}/documents`, { json: b }),
420
- ingest: (id: UUID, b: { ingestion_agent_id: UUID; folder_id?: UUID; documents: { gcs_uri: string; custom_metadata?: Record<string, unknown>; visibility?: Visibility }[] }) =>
421
- this.request<{ parse_job_id: UUID }>("POST", `/v1/corpora/${id}/ingest`, { json: b }),
392
+ /** Count documents in the corpus: { total, indexed, in_flight, failed }. */
393
+ countDocuments: (id: UUID) =>
394
+ this.request<DocumentCount>("GET", `/v1/corpora/${id}/documents/count`),
395
+ /** Upload a file and ingest it ASYNCHRONOUSLY resolves with the created
396
+ * document at status `pending` (HTTP 202); track via documents.get() polling
397
+ * the status to `indexed`/`failed`, or the tenant result webhook. */
398
+ ingestDocument: (id: UUID, a: { file: FileData; filename: string; content_type?: string; visibility?: Visibility; folder_id?: UUID; custom_metadata?: Record<string, unknown> }) => {
399
+ const fd = new FormData();
400
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
401
+ if (a.custom_metadata) fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
402
+ if (a.visibility) fd.append("visibility", a.visibility);
403
+ if (a.folder_id) fd.append("folder_id", a.folder_id);
404
+ return this.request<Document>("POST", `/v1/corpora/${id}/documents`, { body: fd });
405
+ },
422
406
  };
423
407
 
408
+ // --- parsing (utility: file -> text, stores nothing) ---
409
+ parse(a: { file: FileData; filename?: string; content_type?: string }): Promise<{ text: string }> {
410
+ const fd = new FormData();
411
+ fd.append("file", toBlob(a.file, a.content_type), a.filename ?? "document");
412
+ return this.request<{ text: string }>("POST", "/v1/documents/parse", { body: fd });
413
+ }
414
+
424
415
  // --- folders ---
425
416
  folders = {
426
417
  create: (corpusId: UUID, name: string) => this.request<Folder>("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
@@ -428,14 +419,18 @@ export class KnowledgeCoreClient extends HttpBase {
428
419
  delete: (folderId: UUID) => this.request<void>("DELETE", `/v1/folders/${folderId}`),
429
420
  listDocuments: (folderId: UUID, q?: { limit?: number; cursor?: string }) =>
430
421
  this.request<Page<Document>>("GET", `/v1/folders/${folderId}/documents`, { query: q }),
422
+ /** Count documents in the folder: { total, indexed, in_flight, failed }. */
423
+ countDocuments: (folderId: UUID) =>
424
+ this.request<DocumentCount>("GET", `/v1/folders/${folderId}/documents/count`),
431
425
  };
432
426
 
433
427
  // --- documents ---
434
428
  documents = {
435
429
  get: (id: UUID) => this.request<Document>("GET", `/v1/documents/${id}`),
436
- getMetadata: (id: UUID) => this.request<{ document_id: UUID; custom_metadata: Record<string, unknown> }>("GET", `/v1/documents/${id}/metadata`),
437
- patchMetadata: (id: UUID, custom_metadata: Record<string, unknown>) =>
438
- this.request<{ document_id: UUID; custom_metadata: Record<string, unknown> }>("PATCH", `/v1/documents/${id}/metadata`, { json: { custom_metadata } }),
430
+ getCustomMetadata: (id: UUID) => this.request<Record<string, unknown>>("GET", `/v1/documents/${id}/custom_metadata`),
431
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
432
+ patchCustomMetadata: (id: UUID, custom_metadata: Record<string, unknown>) =>
433
+ this.request<Record<string, unknown>>("PATCH", `/v1/documents/${id}/custom_metadata`, { json: custom_metadata }),
439
434
  update: (id: UUID, b: { visibility?: Visibility; folder_id?: UUID }) => this.request<Document>("PATCH", `/v1/documents/${id}`, { json: b }),
440
435
  contentUrl: (id: UUID, disposition: "inline" | "attachment" = "inline") =>
441
436
  this.request<ContentUrl>("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
@@ -460,29 +455,19 @@ export class KnowledgeCoreClient extends HttpBase {
460
455
 
461
456
  // --- conversation attachments (pinned full-document review) ---
462
457
  attachments = {
463
- /** Upload + attach a document. `data` is the raw file bytes. Returns status=parsing. */
464
- upload: (conversationId: UUID, a: { filename: string; content_type: string; data: BodyInit; custom_metadata?: Record<string, unknown> }) =>
465
- this.request<ConversationDocument>("POST", `/v1/conversations/${conversationId}/documents`, {
466
- body: a.data,
467
- headers: { "content-type": a.content_type },
468
- query: { filename: a.filename, content_type: a.content_type, custom_metadata: a.custom_metadata ? JSON.stringify(a.custom_metadata) : undefined },
469
- }),
458
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
459
+ upload: (conversationId: UUID, a: { file: FileData; filename: string; content_type?: string; custom_metadata?: Record<string, unknown> }) => {
460
+ const fd = new FormData();
461
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
462
+ if (a.custom_metadata) fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
463
+ return this.request<Attachment>("POST", `/v1/conversations/${conversationId}/documents`, { body: fd });
464
+ },
470
465
  list: (conversationId: UUID, q?: { limit?: number; cursor?: string }) =>
471
- this.request<Page<ConversationDocument>>("GET", `/v1/conversations/${conversationId}/documents`, { query: q }),
472
- get: (conversationId: UUID, docId: UUID) => this.request<ConversationDocument>("GET", `/v1/conversations/${conversationId}/documents/${docId}`),
466
+ this.request<Page<Attachment>>("GET", `/v1/conversations/${conversationId}/documents`, { query: q }),
467
+ get: (conversationId: UUID, docId: UUID) => this.request<Attachment>("GET", `/v1/conversations/${conversationId}/documents/${docId}`),
473
468
  contentUrl: (conversationId: UUID, docId: UUID, disposition: "inline" | "attachment" = "inline") =>
474
469
  this.request<ContentUrl>("GET", `/v1/conversations/${conversationId}/documents/${docId}/content-url`, { query: { disposition } }),
475
470
  delete: (conversationId: UUID, docId: UUID) => this.request<void>("DELETE", `/v1/conversations/${conversationId}/documents/${docId}`),
476
- /** Poll until the attachment is ready or failed (or timeout). */
477
- waitReady: async (conversationId: UUID, docId: UUID, opts: { intervalMs?: number; timeoutMs?: number } = {}): Promise<ConversationDocument> => {
478
- const interval = opts.intervalMs ?? 3000;
479
- const deadline = Date.now() + (opts.timeoutMs ?? 180_000);
480
- for (;;) {
481
- const d = await this.attachments.get(conversationId, docId);
482
- if (d.status === "ready" || d.status === "failed" || Date.now() > deadline) return d;
483
- await sleep(interval);
484
- }
485
- },
486
471
  };
487
472
 
488
473
  // --- messages + feedback ---
@@ -498,15 +483,9 @@ export class KnowledgeCoreClient extends HttpBase {
498
483
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/feedback/${id}`),
499
484
  };
500
485
 
501
- // --- parse jobs ---
502
- parseJobs = {
503
- list: (q?: { status_filter?: string; limit?: number; cursor?: string }) => this.request<Page<ParseJob>>("GET", "/v1/parse_jobs", { query: q }),
504
- get: (id: UUID) => this.request<ParseJob>("GET", `/v1/parse_jobs/${id}`),
505
- };
506
-
507
486
  // --- agents (tenant read-only: choose an agent to query) ---
508
487
  agents = {
509
- list: (q?: { kind?: "query" | "ingestion"; limit?: number; cursor?: string }) => this.request<Page<Agent>>("GET", "/v1/agents", { query: q }),
488
+ list: (q?: { limit?: number; cursor?: string }) => this.request<Page<Agent>>("GET", "/v1/agents", { query: q }),
510
489
  listAll: () => this.pageAll<Agent>("/v1/agents"),
511
490
  get: (id: UUID) => this.request<Agent>("GET", `/v1/agents/${id}`),
512
491
  };
@@ -524,15 +503,15 @@ export class AdminClient extends HttpBase {
524
503
  delete: (id: UUID, confirmName: string) => this.request<void>("DELETE", `/v1/tenants/${id}`, { query: { confirm: confirmName } }),
525
504
  /** Mint a tenant API key — the raw key is in the response ONCE; store it now. */
526
505
  createApiKey: (tenantId: UUID, label?: string) => this.request<ApiKeyCreated>("POST", `/v1/tenants/${tenantId}/api-keys`, { json: { label } }),
527
- listApiKeys: (tenantId: UUID, q?: { limit?: number; cursor?: string }) => this.request<Page<ApiKeyInfo>>("GET", `/v1/tenants/${tenantId}/api-keys`, { query: q }),
506
+ listApiKeys: (tenantId: UUID, q?: { limit?: number; cursor?: string }) => this.request<Page<ApiKey>>("GET", `/v1/tenants/${tenantId}/api-keys`, { query: q }),
528
507
  revokeApiKey: (tenantId: UUID, keyId: UUID) => this.request<void>("DELETE", `/v1/tenants/${tenantId}/api-keys/${keyId}`),
529
508
  };
530
509
 
510
+ // Agents are query agents, global for now.
531
511
  agents = {
532
- /** tenant_id explicit, or null for a global/shared agent. */
533
- create: (b: { name: string; kind: "query" | "ingestion"; tenant_id?: UUID | null; generation_model?: string; top_k?: number; rerank_instruction?: string; max_hops?: number; groundedness_threshold?: number; grounding_enabled?: boolean }) =>
512
+ create: (b: { name: string; generation_model?: string; top_k?: number; rerank_instruction?: string; max_hops?: number; groundedness_threshold?: number; grounding_enabled?: boolean }) =>
534
513
  this.request<Agent>("POST", "/v1/agents", { json: b }),
535
- update: (id: UUID, b: Partial<Omit<Agent, "id" | "kind" | "tenant_id" | "created_at" | "updated_at">>) =>
514
+ update: (id: UUID, b: Partial<Omit<Agent, "id">>) =>
536
515
  this.request<Agent>("PATCH", `/v1/agents/${id}`, { json: b }),
537
516
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/agents/${id}`),
538
517
  };
@@ -560,7 +539,3 @@ function dispatchSse(frame: string, h: StreamHandlers): void {
560
539
  case "done": h.onDone?.(); break;
561
540
  }
562
541
  }
563
-
564
- function sleep(ms: number): Promise<void> {
565
- return new Promise((r) => setTimeout(r, ms));
566
- }