@babav/knowledge-core-client 0.1.0 → 0.2.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,38 +83,25 @@ 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;
@@ -120,13 +110,10 @@ export interface ContentUrl {
120
110
  }
121
111
  export interface Conversation {
122
112
  id: UUID;
123
- tenant_id: UUID | null;
124
113
  title: string | null;
125
114
  custom_metadata: Record<string, unknown>;
126
115
  ephemeral: boolean;
127
116
  last_activity: string;
128
- created_at: string;
129
- updated_at: string;
130
117
  }
131
118
  export interface Message {
132
119
  id: UUID;
@@ -138,55 +125,33 @@ export interface Message {
138
125
  citations: Record<string, unknown> | null;
139
126
  groundedness: Record<string, unknown> | null;
140
127
  retrieval_contents: unknown[] | null;
141
- retrieval_debug: Record<string, unknown> | null;
142
128
  usage: Record<string, unknown> | null;
143
- created_at: string;
144
129
  }
145
- export interface ConversationDocument {
130
+ export interface Attachment {
146
131
  id: UUID;
147
132
  conversation_id: UUID;
148
- tenant_id: UUID;
149
133
  filename: string | null;
150
134
  content_type: string | null;
151
135
  byte_size: number | null;
152
136
  status: "parsing" | "ready" | "failed";
153
137
  custom_metadata: Record<string, unknown> | null;
154
138
  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
139
  }
169
140
  export interface Feedback {
170
141
  id: UUID;
171
142
  message_id: UUID;
172
143
  rating: number | null;
173
144
  comment: string | null;
174
- created_at: string;
175
- updated_at: string;
176
145
  }
177
146
  export interface Agent {
178
147
  id: UUID;
179
148
  name: string;
180
- kind: "query" | "ingestion";
181
- tenant_id: UUID | null;
182
149
  generation_model: string | null;
183
150
  top_k: number | null;
184
151
  rerank_instruction: string | null;
185
152
  max_hops: number | null;
186
153
  groundedness_threshold: number | null;
187
154
  grounding_enabled: boolean | null;
188
- created_at: string;
189
- updated_at: string;
190
155
  }
191
156
  export interface Tenant {
192
157
  id: UUID;
@@ -198,25 +163,18 @@ export interface Tenant {
198
163
  query_endpoint: string | null;
199
164
  query_public_domain: string | null;
200
165
  query_deployed_index_id: string | null;
201
- created_at: string;
202
- updated_at: string;
203
166
  }
204
167
  export interface ApiKeyCreated {
205
168
  id: UUID;
206
169
  api_key: string;
207
170
  prefix: string;
208
171
  label: string | null;
209
- created_at: string;
210
172
  }
211
- export interface ApiKeyInfo {
173
+ export interface ApiKey {
212
174
  id: UUID;
213
- tenant_id: UUID;
214
175
  prefix: string;
215
176
  label: string | null;
216
- last_used_at: string | null;
217
- revoked_at: string | null;
218
- created_at: string;
219
- updated_at: string;
177
+ revoked: boolean;
220
178
  }
221
179
  /** Thrown on any non-2xx response. `detail` is the structured body when present
222
180
  * (e.g. {error:"attachments_pending", attachments:[...]} or
@@ -232,7 +190,8 @@ export declare class KnowledgeCoreError extends Error {
232
190
  export interface ClientOptions {
233
191
  baseUrl: string;
234
192
  apiKey: string;
235
- /** Optional default fetch timeout (ms). Streaming ignores this. */
193
+ /** Optional default fetch timeout (ms). Streaming ignores this. Note:
194
+ * synchronous ingest can take a while — set this generously or omit. */
236
195
  timeoutMs?: number;
237
196
  fetch?: typeof fetch;
238
197
  }
@@ -289,9 +248,7 @@ export declare class KnowledgeCoreClient extends HttpBase {
289
248
  corpora: {
290
249
  create: (b: {
291
250
  name: string;
292
- embedding_model: string;
293
251
  description?: string;
294
- external_ref?: string;
295
252
  }) => Promise<Corpus>;
296
253
  list: (q?: {
297
254
  limit?: number;
@@ -302,7 +259,6 @@ export declare class KnowledgeCoreClient extends HttpBase {
302
259
  update: (id: UUID, b: {
303
260
  name?: string;
304
261
  description?: string;
305
- external_ref?: string;
306
262
  }) => Promise<Corpus>;
307
263
  delete: (id: UUID, confirmName: string) => Promise<void>;
308
264
  listFolders: (id: UUID) => Promise<Folder[]>;
@@ -310,26 +266,24 @@ export declare class KnowledgeCoreClient extends HttpBase {
310
266
  limit?: number;
311
267
  cursor?: string;
312
268
  }) => Promise<Page<Document>>;
313
- createDocument: (id: UUID, b: {
314
- source_uri: string;
315
- folder_id?: UUID;
269
+ /** Upload a file and ingest it SYNCHRONOUSLY — resolves once the document is
270
+ * indexed (or rejects 502 on a pipeline failure). */
271
+ ingestDocument: (id: UUID, a: {
272
+ file: FileData;
273
+ filename: string;
274
+ content_type?: string;
316
275
  visibility?: Visibility;
317
- status?: string;
318
- parse_job_id?: UUID;
276
+ folder_id?: UUID;
319
277
  custom_metadata?: Record<string, unknown>;
320
278
  }) => 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
279
  };
280
+ parse(a: {
281
+ file: FileData;
282
+ filename?: string;
283
+ content_type?: string;
284
+ }): Promise<{
285
+ text: string;
286
+ }>;
333
287
  folders: {
334
288
  create: (corpusId: UUID, name: string) => Promise<Folder>;
335
289
  rename: (folderId: UUID, name: string) => Promise<Folder>;
@@ -341,14 +295,9 @@ export declare class KnowledgeCoreClient extends HttpBase {
341
295
  };
342
296
  documents: {
343
297
  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
- }>;
298
+ getCustomMetadata: (id: UUID) => Promise<Record<string, unknown>>;
299
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
300
+ patchCustomMetadata: (id: UUID, custom_metadata: Record<string, unknown>) => Promise<Record<string, unknown>>;
352
301
  update: (id: UUID, b: {
353
302
  visibility?: Visibility;
354
303
  folder_id?: UUID;
@@ -384,25 +333,20 @@ export declare class KnowledgeCoreClient extends HttpBase {
384
333
  }) => Promise<Page<Message>>;
385
334
  };
386
335
  attachments: {
387
- /** Upload + attach a document. `data` is the raw file bytes. Returns status=parsing. */
336
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
388
337
  upload: (conversationId: UUID, a: {
338
+ file: FileData;
389
339
  filename: string;
390
- content_type: string;
391
- data: BodyInit;
340
+ content_type?: string;
392
341
  custom_metadata?: Record<string, unknown>;
393
- }) => Promise<ConversationDocument>;
342
+ }) => Promise<Attachment>;
394
343
  list: (conversationId: UUID, q?: {
395
344
  limit?: number;
396
345
  cursor?: string;
397
- }) => Promise<Page<ConversationDocument>>;
398
- get: (conversationId: UUID, docId: UUID) => Promise<ConversationDocument>;
346
+ }) => Promise<Page<Attachment>>;
347
+ get: (conversationId: UUID, docId: UUID) => Promise<Attachment>;
399
348
  contentUrl: (conversationId: UUID, docId: UUID, disposition?: "inline" | "attachment") => Promise<ContentUrl>;
400
349
  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
350
  };
407
351
  messages: {
408
352
  get: (id: UUID) => Promise<Message>;
@@ -419,17 +363,8 @@ export declare class KnowledgeCoreClient extends HttpBase {
419
363
  get: (id: UUID) => Promise<Feedback>;
420
364
  delete: (id: UUID) => Promise<void>;
421
365
  };
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
366
  agents: {
431
367
  list: (q?: {
432
- kind?: "query" | "ingestion";
433
368
  limit?: number;
434
369
  cursor?: string;
435
370
  }) => Promise<Page<Agent>>;
@@ -454,15 +389,12 @@ export declare class AdminClient extends HttpBase {
454
389
  listApiKeys: (tenantId: UUID, q?: {
455
390
  limit?: number;
456
391
  cursor?: string;
457
- }) => Promise<Page<ApiKeyInfo>>;
392
+ }) => Promise<Page<ApiKey>>;
458
393
  revokeApiKey: (tenantId: UUID, keyId: UUID) => Promise<void>;
459
394
  };
460
395
  agents: {
461
- /** tenant_id explicit, or null for a global/shared agent. */
462
396
  create: (b: {
463
397
  name: string;
464
- kind: "query" | "ingestion";
465
- tenant_id?: UUID | null;
466
398
  generation_model?: string;
467
399
  top_k?: number;
468
400
  rerank_instruction?: string;
@@ -470,7 +402,7 @@ export declare class AdminClient extends HttpBase {
470
402
  groundedness_threshold?: number;
471
403
  grounding_enabled?: boolean;
472
404
  }) => Promise<Agent>;
473
- update: (id: UUID, b: Partial<Omit<Agent, "id" | "kind" | "tenant_id" | "created_at" | "updated_at">>) => Promise<Agent>;
405
+ update: (id: UUID, b: Partial<Omit<Agent, "id">>) => Promise<Agent>;
474
406
  delete: (id: UUID) => Promise<void>;
475
407
  };
476
408
  }
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,9 +164,26 @@ 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
+ /** Upload a file and ingest it SYNCHRONOUSLY resolves once the document is
168
+ * indexed (or rejects 502 on a pipeline failure). */
169
+ ingestDocument: (id, a) => {
170
+ const fd = new FormData();
171
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
172
+ if (a.custom_metadata)
173
+ fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
174
+ if (a.visibility)
175
+ fd.append("visibility", a.visibility);
176
+ if (a.folder_id)
177
+ fd.append("folder_id", a.folder_id);
178
+ return this.request("POST", `/v1/corpora/${id}/documents`, { body: fd });
179
+ },
162
180
  };
181
+ // --- parsing (utility: file -> text, stores nothing) ---
182
+ parse(a) {
183
+ const fd = new FormData();
184
+ fd.append("file", toBlob(a.file, a.content_type), a.filename ?? "document");
185
+ return this.request("POST", "/v1/documents/parse", { body: fd });
186
+ }
163
187
  // --- folders ---
164
188
  folders = {
165
189
  create: (corpusId, name) => this.request("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
@@ -170,8 +194,9 @@ export class KnowledgeCoreClient extends HttpBase {
170
194
  // --- documents ---
171
195
  documents = {
172
196
  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 } }),
197
+ getCustomMetadata: (id) => this.request("GET", `/v1/documents/${id}/custom_metadata`),
198
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
199
+ patchCustomMetadata: (id, custom_metadata) => this.request("PATCH", `/v1/documents/${id}/custom_metadata`, { json: custom_metadata }),
175
200
  update: (id, b) => this.request("PATCH", `/v1/documents/${id}`, { json: b }),
176
201
  contentUrl: (id, disposition = "inline") => this.request("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
177
202
  delete: (id) => this.request("DELETE", `/v1/documents/${id}`),
@@ -189,27 +214,18 @@ export class KnowledgeCoreClient extends HttpBase {
189
214
  };
190
215
  // --- conversation attachments (pinned full-document review) ---
191
216
  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
- }),
217
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
218
+ upload: (conversationId, a) => {
219
+ const fd = new FormData();
220
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
221
+ if (a.custom_metadata)
222
+ fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
223
+ return this.request("POST", `/v1/conversations/${conversationId}/documents`, { body: fd });
224
+ },
198
225
  list: (conversationId, q) => this.request("GET", `/v1/conversations/${conversationId}/documents`, { query: q }),
199
226
  get: (conversationId, docId) => this.request("GET", `/v1/conversations/${conversationId}/documents/${docId}`),
200
227
  contentUrl: (conversationId, docId, disposition = "inline") => this.request("GET", `/v1/conversations/${conversationId}/documents/${docId}/content-url`, { query: { disposition } }),
201
228
  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
229
  };
214
230
  // --- messages + feedback ---
215
231
  messages = {
@@ -221,11 +237,6 @@ export class KnowledgeCoreClient extends HttpBase {
221
237
  get: (id) => this.request("GET", `/v1/feedback/${id}`),
222
238
  delete: (id) => this.request("DELETE", `/v1/feedback/${id}`),
223
239
  };
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
240
  // --- agents (tenant read-only: choose an agent to query) ---
230
241
  agents = {
231
242
  list: (q) => this.request("GET", "/v1/agents", { query: q }),
@@ -248,8 +259,8 @@ export class AdminClient extends HttpBase {
248
259
  listApiKeys: (tenantId, q) => this.request("GET", `/v1/tenants/${tenantId}/api-keys`, { query: q }),
249
260
  revokeApiKey: (tenantId, keyId) => this.request("DELETE", `/v1/tenants/${tenantId}/api-keys/${keyId}`),
250
261
  };
262
+ // Agents are query agents, global for now.
251
263
  agents = {
252
- /** tenant_id explicit, or null for a global/shared agent. */
253
264
  create: (b) => this.request("POST", "/v1/agents", { json: b }),
254
265
  update: (id, b) => this.request("PATCH", `/v1/agents/${id}`, { json: b }),
255
266
  delete: (id) => this.request("DELETE", `/v1/agents/${id}`),
@@ -292,6 +303,3 @@ function dispatchSse(frame, h) {
292
303
  break;
293
304
  }
294
305
  }
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.2.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,38 +97,25 @@ 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;
@@ -133,13 +124,10 @@ export interface ContentUrl {
133
124
  }
134
125
  export interface Conversation {
135
126
  id: UUID;
136
- tenant_id: UUID | null;
137
127
  title: string | null;
138
128
  custom_metadata: Record<string, unknown>;
139
129
  ephemeral: boolean;
140
130
  last_activity: string;
141
- created_at: string;
142
- updated_at: string;
143
131
  }
144
132
  export interface Message {
145
133
  id: UUID;
@@ -151,55 +139,33 @@ export interface Message {
151
139
  citations: Record<string, unknown> | null;
152
140
  groundedness: Record<string, unknown> | null;
153
141
  retrieval_contents: unknown[] | null;
154
- retrieval_debug: Record<string, unknown> | null;
155
142
  usage: Record<string, unknown> | null;
156
- created_at: string;
157
143
  }
158
- export interface ConversationDocument {
144
+ export interface Attachment {
159
145
  id: UUID;
160
146
  conversation_id: UUID;
161
- tenant_id: UUID;
162
147
  filename: string | null;
163
148
  content_type: string | null;
164
149
  byte_size: number | null;
165
150
  status: "parsing" | "ready" | "failed";
166
151
  custom_metadata: Record<string, unknown> | null;
167
152
  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
153
  }
182
154
  export interface Feedback {
183
155
  id: UUID;
184
156
  message_id: UUID;
185
157
  rating: number | null;
186
158
  comment: string | null;
187
- created_at: string;
188
- updated_at: string;
189
159
  }
190
160
  export interface Agent {
191
161
  id: UUID;
192
162
  name: string;
193
- kind: "query" | "ingestion";
194
- tenant_id: UUID | null;
195
163
  generation_model: string | null;
196
164
  top_k: number | null;
197
165
  rerank_instruction: string | null;
198
166
  max_hops: number | null;
199
167
  groundedness_threshold: number | null;
200
168
  grounding_enabled: boolean | null;
201
- created_at: string;
202
- updated_at: string;
203
169
  }
204
170
  export interface Tenant {
205
171
  id: UUID;
@@ -211,25 +177,18 @@ export interface Tenant {
211
177
  query_endpoint: string | null;
212
178
  query_public_domain: string | null;
213
179
  query_deployed_index_id: string | null;
214
- created_at: string;
215
- updated_at: string;
216
180
  }
217
181
  export interface ApiKeyCreated {
218
182
  id: UUID;
219
183
  api_key: string; // shown ONCE — store it now
220
184
  prefix: string;
221
185
  label: string | null;
222
- created_at: string;
223
186
  }
224
- export interface ApiKeyInfo {
187
+ export interface ApiKey {
225
188
  id: UUID;
226
- tenant_id: UUID;
227
189
  prefix: string;
228
190
  label: string | null;
229
- last_used_at: string | null;
230
- revoked_at: string | null;
231
- created_at: string;
232
- updated_at: string;
191
+ revoked: boolean;
233
192
  }
234
193
 
235
194
  // ---------------------------------------------------------------------------
@@ -260,7 +219,8 @@ export class KnowledgeCoreError extends Error {
260
219
  export interface ClientOptions {
261
220
  baseUrl: string;
262
221
  apiKey: string;
263
- /** Optional default fetch timeout (ms). Streaming ignores this. */
222
+ /** Optional default fetch timeout (ms). Streaming ignores this. Note:
223
+ * synchronous ingest can take a while — set this generously or omit. */
264
224
  timeoutMs?: number;
265
225
  fetch?: typeof fetch; // override for tests
266
226
  }
@@ -268,7 +228,7 @@ export interface ClientOptions {
268
228
  interface RequestOpts {
269
229
  query?: Record<string, string | number | boolean | undefined>;
270
230
  json?: unknown;
271
- body?: BodyInit; // raw body (attachment upload)
231
+ body?: BodyInit; // raw body / multipart FormData (file uploads)
272
232
  headers?: Record<string, string>;
273
233
  signal?: AbortSignal;
274
234
  }
@@ -303,6 +263,7 @@ class HttpBase {
303
263
  headers["content-type"] = "application/json";
304
264
  body = JSON.stringify(opts.json);
305
265
  } else if (opts.body !== undefined) {
266
+ // FormData sets its own multipart content-type (with boundary) on fetch.
306
267
  body = opts.body;
307
268
  }
308
269
  let signal = opts.signal;
@@ -350,6 +311,11 @@ function safeJson(t: string): unknown {
350
311
  }
351
312
  }
352
313
 
314
+ function toBlob(data: FileData, contentType?: string): Blob {
315
+ if (data instanceof Blob) return data;
316
+ return new Blob([data as BlobPart], { type: contentType ?? "application/octet-stream" });
317
+ }
318
+
353
319
  // ---------------------------------------------------------------------------
354
320
  // SSE handlers
355
321
  // ---------------------------------------------------------------------------
@@ -404,23 +370,36 @@ export class KnowledgeCoreClient extends HttpBase {
404
370
 
405
371
  // --- corpora ---
406
372
  corpora = {
407
- create: (b: { name: string; embedding_model: string; description?: string; external_ref?: string }) =>
373
+ create: (b: { name: string; description?: string }) =>
408
374
  this.request<Corpus>("POST", "/v1/corpora", { json: b }),
409
375
  list: (q?: { limit?: number; cursor?: string }) => this.request<Page<Corpus>>("GET", "/v1/corpora", { query: q }),
410
376
  listAll: () => this.pageAll<Corpus>("/v1/corpora"),
411
377
  get: (id: UUID) => this.request<Corpus>("GET", `/v1/corpora/${id}`),
412
- update: (id: UUID, b: { name?: string; description?: string; external_ref?: string }) =>
378
+ update: (id: UUID, b: { name?: string; description?: string }) =>
413
379
  this.request<Corpus>("PATCH", `/v1/corpora/${id}`, { json: b }),
414
380
  delete: (id: UUID, confirmName: string) => this.request<void>("DELETE", `/v1/corpora/${id}`, { query: { confirm: confirmName } }),
415
381
  listFolders: (id: UUID) => this.pageAll<Folder>(`/v1/corpora/${id}/folders`),
416
382
  listDocuments: (id: UUID, q?: { limit?: number; cursor?: string }) =>
417
383
  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 }),
384
+ /** Upload a file and ingest it SYNCHRONOUSLY resolves once the document is
385
+ * indexed (or rejects 502 on a pipeline failure). */
386
+ ingestDocument: (id: UUID, a: { file: FileData; filename: string; content_type?: string; visibility?: Visibility; folder_id?: UUID; custom_metadata?: Record<string, unknown> }) => {
387
+ const fd = new FormData();
388
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
389
+ if (a.custom_metadata) fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
390
+ if (a.visibility) fd.append("visibility", a.visibility);
391
+ if (a.folder_id) fd.append("folder_id", a.folder_id);
392
+ return this.request<Document>("POST", `/v1/corpora/${id}/documents`, { body: fd });
393
+ },
422
394
  };
423
395
 
396
+ // --- parsing (utility: file -> text, stores nothing) ---
397
+ parse(a: { file: FileData; filename?: string; content_type?: string }): Promise<{ text: string }> {
398
+ const fd = new FormData();
399
+ fd.append("file", toBlob(a.file, a.content_type), a.filename ?? "document");
400
+ return this.request<{ text: string }>("POST", "/v1/documents/parse", { body: fd });
401
+ }
402
+
424
403
  // --- folders ---
425
404
  folders = {
426
405
  create: (corpusId: UUID, name: string) => this.request<Folder>("POST", `/v1/corpora/${corpusId}/folders`, { json: { name } }),
@@ -433,9 +412,10 @@ export class KnowledgeCoreClient extends HttpBase {
433
412
  // --- documents ---
434
413
  documents = {
435
414
  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 } }),
415
+ getCustomMetadata: (id: UUID) => this.request<Record<string, unknown>>("GET", `/v1/documents/${id}/custom_metadata`),
416
+ /** Merge custom_metadata (a null value deletes a key). Returns the merged object. */
417
+ patchCustomMetadata: (id: UUID, custom_metadata: Record<string, unknown>) =>
418
+ this.request<Record<string, unknown>>("PATCH", `/v1/documents/${id}/custom_metadata`, { json: custom_metadata }),
439
419
  update: (id: UUID, b: { visibility?: Visibility; folder_id?: UUID }) => this.request<Document>("PATCH", `/v1/documents/${id}`, { json: b }),
440
420
  contentUrl: (id: UUID, disposition: "inline" | "attachment" = "inline") =>
441
421
  this.request<ContentUrl>("GET", `/v1/documents/${id}/content-url`, { query: { disposition } }),
@@ -460,29 +440,19 @@ export class KnowledgeCoreClient extends HttpBase {
460
440
 
461
441
  // --- conversation attachments (pinned full-document review) ---
462
442
  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
- }),
443
+ /** Upload + attach a file and parse it SYNCHRONOUSLY resolves with status=ready. */
444
+ upload: (conversationId: UUID, a: { file: FileData; filename: string; content_type?: string; custom_metadata?: Record<string, unknown> }) => {
445
+ const fd = new FormData();
446
+ fd.append("file", toBlob(a.file, a.content_type), a.filename);
447
+ if (a.custom_metadata) fd.append("custom_metadata", JSON.stringify(a.custom_metadata));
448
+ return this.request<Attachment>("POST", `/v1/conversations/${conversationId}/documents`, { body: fd });
449
+ },
470
450
  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}`),
451
+ this.request<Page<Attachment>>("GET", `/v1/conversations/${conversationId}/documents`, { query: q }),
452
+ get: (conversationId: UUID, docId: UUID) => this.request<Attachment>("GET", `/v1/conversations/${conversationId}/documents/${docId}`),
473
453
  contentUrl: (conversationId: UUID, docId: UUID, disposition: "inline" | "attachment" = "inline") =>
474
454
  this.request<ContentUrl>("GET", `/v1/conversations/${conversationId}/documents/${docId}/content-url`, { query: { disposition } }),
475
455
  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
456
  };
487
457
 
488
458
  // --- messages + feedback ---
@@ -498,15 +468,9 @@ export class KnowledgeCoreClient extends HttpBase {
498
468
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/feedback/${id}`),
499
469
  };
500
470
 
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
471
  // --- agents (tenant read-only: choose an agent to query) ---
508
472
  agents = {
509
- list: (q?: { kind?: "query" | "ingestion"; limit?: number; cursor?: string }) => this.request<Page<Agent>>("GET", "/v1/agents", { query: q }),
473
+ list: (q?: { limit?: number; cursor?: string }) => this.request<Page<Agent>>("GET", "/v1/agents", { query: q }),
510
474
  listAll: () => this.pageAll<Agent>("/v1/agents"),
511
475
  get: (id: UUID) => this.request<Agent>("GET", `/v1/agents/${id}`),
512
476
  };
@@ -524,15 +488,15 @@ export class AdminClient extends HttpBase {
524
488
  delete: (id: UUID, confirmName: string) => this.request<void>("DELETE", `/v1/tenants/${id}`, { query: { confirm: confirmName } }),
525
489
  /** Mint a tenant API key — the raw key is in the response ONCE; store it now. */
526
490
  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 }),
491
+ listApiKeys: (tenantId: UUID, q?: { limit?: number; cursor?: string }) => this.request<Page<ApiKey>>("GET", `/v1/tenants/${tenantId}/api-keys`, { query: q }),
528
492
  revokeApiKey: (tenantId: UUID, keyId: UUID) => this.request<void>("DELETE", `/v1/tenants/${tenantId}/api-keys/${keyId}`),
529
493
  };
530
494
 
495
+ // Agents are query agents, global for now.
531
496
  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 }) =>
497
+ create: (b: { name: string; generation_model?: string; top_k?: number; rerank_instruction?: string; max_hops?: number; groundedness_threshold?: number; grounding_enabled?: boolean }) =>
534
498
  this.request<Agent>("POST", "/v1/agents", { json: b }),
535
- update: (id: UUID, b: Partial<Omit<Agent, "id" | "kind" | "tenant_id" | "created_at" | "updated_at">>) =>
499
+ update: (id: UUID, b: Partial<Omit<Agent, "id">>) =>
536
500
  this.request<Agent>("PATCH", `/v1/agents/${id}`, { json: b }),
537
501
  delete: (id: UUID) => this.request<void>("DELETE", `/v1/agents/${id}`),
538
502
  };
@@ -560,7 +524,3 @@ function dispatchSse(frame: string, h: StreamHandlers): void {
560
524
  case "done": h.onDone?.(); break;
561
525
  }
562
526
  }
563
-
564
- function sleep(ms: number): Promise<void> {
565
- return new Promise((r) => setTimeout(r, ms));
566
- }