@oberik/sdk 0.65.0 → 0.67.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/esm/index.js CHANGED
@@ -758,6 +758,25 @@ function asBlob(input) {
758
758
  // handing that straight to Blob can carry bytes belonging to something else.
759
759
  return new Blob([bytes.slice()]);
760
760
  }
761
+ /**
762
+ * The name a document is stored under: what the caller passed, the file's own, or a
763
+ * placeholder.
764
+ *
765
+ * One function for both clients, because they disagreed about it. The project client
766
+ * derived a name and fell back to `"upload.bin"`; the end-user client took `filename` in a
767
+ * **required** options object, so a JavaScript caller writing the documented
768
+ * `uploadAndWait(file)` got `TypeError: Cannot read properties of undefined (reading
769
+ * 'filename')` — thrown from inside the SDK, with nothing in the stack naming Oberik, at
770
+ * the reader most likely to have copied the snippet (OBE-195).
771
+ *
772
+ * Both are optional now and both land here. `"upload.bin"` rather than a refusal: a `File`
773
+ * carries its name and a Blob, Buffer or ArrayBuffer does not, and an upload that arrives
774
+ * with a placeholder name is visibly wrong in the listing, where a caller can fix it with
775
+ * `documents.update`. Pass `filename` — it is what the document is listed and cited under.
776
+ */
777
+ function uploadName(file, filename) {
778
+ return filename || file?.name || "upload.bin";
779
+ }
761
780
  /** Normalise whatever an `onQuestion` handler returned into an answer the API accepts.
762
781
  *
763
782
  * A handler returning a bare string was silently ignored. `{ ...answer }` on a string
@@ -2346,17 +2365,35 @@ export class AgentFramework {
2346
2365
  },
2347
2366
  };
2348
2367
  documents = {
2368
+ /**
2369
+ * One PAGE of documents, newest first — `{ items, has_more, next_offset }`.
2370
+ *
2371
+ * It was a bare array, which asserts completeness by omission, on the one collection
2372
+ * that only grows: a live project of 302 documents returned all 302 and 181KB on every
2373
+ * call, and a 10k corpus would be ~6MB on a route a dashboard opens with (OBE-190).
2374
+ * `limit` defaults to 100 and is capped at 500; page with `next_offset`, which is given
2375
+ * rather than left as arithmetic because the cap can make `offset + limit` wrong.
2376
+ *
2377
+ * `with_total` adds a `total` and costs a second query — for a summary, not for paging.
2378
+ * "Is there another page" is `has_more`, and it is free.
2379
+ */
2349
2380
  list: (query = {}) => this.request("GET", "/documents", { query }),
2350
2381
  get: (id) => this.request("GET", `/documents/${id}`),
2351
2382
  delete: (id) => this.request("DELETE", `/documents/${id}`),
2352
2383
  retrieve: (body) => this.request("POST", "/documents/retrieve", { body }),
2353
2384
  /** Small-file convenience upload via multipart form (server proxies to S3). */
2354
- uploadSimple: async (file, opts) => {
2385
+ uploadSimple: async (file,
2386
+ // Optional, and the same shape the project client takes: this signature required an
2387
+ // options object, so `uploadSimple(file)` and `uploadAndWait(file)` — the way the
2388
+ // docs write them — threw a TypeError out of the SDK's own body instead of saying
2389
+ // what was missing (OBE-195).
2390
+ opts = {}) => {
2391
+ const filename = uploadName(file, opts.filename);
2355
2392
  const form = new FormData();
2356
2393
  const blob = typeof Blob !== "undefined" && file instanceof Blob
2357
2394
  ? file
2358
2395
  : new Blob([file], { type: opts.contentType ?? "application/octet-stream" });
2359
- form.append("file", blob, opts.filename);
2396
+ form.append("file", blob, filename);
2360
2397
  form.append("tags", (opts.tags ?? []).join(","));
2361
2398
  // Set visibility AT UPLOAD, not after: a document is retrievable as soon as
2362
2399
  // ingestion finishes, so patching later leaves a window where it's readable by
@@ -2389,7 +2426,7 @@ export class AgentFramework {
2389
2426
  return (await res.json());
2390
2427
  },
2391
2428
  /** Resumable presigned multipart upload (direct to S3). */
2392
- upload: (file, opts) => this.multipartUpload(file, opts),
2429
+ upload: (file, opts = {}) => this.multipartUpload(file, opts),
2393
2430
  /** Poll a document until ingestion finishes (status "ready" or "failed").
2394
2431
  * Ingestion is async, so querying a just-uploaded doc may return nothing until
2395
2432
  * this resolves. Throws on "failed" or timeout. */
@@ -2413,7 +2450,7 @@ export class AgentFramework {
2413
2450
  // Takes the ACL fields too: this is the upload most callers use, so omitting them
2414
2451
  // here forced a second `patch` and left a window where the document was already
2415
2452
  // retrievable at its default visibility.
2416
- opts) => {
2453
+ opts = {}) => {
2417
2454
  const doc = await this.documents.uploadSimple(file, opts);
2418
2455
  return this.documents.waitReady(doc.id, { timeoutMs: opts.timeoutMs, signal: opts.signal });
2419
2456
  },
@@ -3074,7 +3111,7 @@ export class AgentFramework {
3074
3111
  const maxRetries = opts.maxRetries ?? 5;
3075
3112
  const presign = await this.request("POST", "/documents/presign-upload", {
3076
3113
  body: {
3077
- filename: opts.filename,
3114
+ filename: uploadName(file, opts.filename),
3078
3115
  content_type: opts.contentType,
3079
3116
  tags: opts.tags ?? [],
3080
3117
  ...(opts.visibility ? { visibility: opts.visibility } : {}),
@@ -3353,6 +3390,9 @@ export class OberikProject {
3353
3390
  * turning citation markers off, meant dropping to raw HTTP through `raw()`.
3354
3391
  */
3355
3392
  project = {
3393
+ /** The project, its capability ceiling and its settings — plus `readiness` and the
3394
+ * `health` / `healthDetail` badge, so "is this project working" is answered by the
3395
+ * first thing you fetch rather than one route further on. */
3356
3396
  get: () => this.request("GET", ""),
3357
3397
  /** Merges — send only what you want to change. */
3358
3398
  set: (fields) => this.request("PATCH", "", fields),
@@ -3426,9 +3466,26 @@ export class OberikProject {
3426
3466
  * accepted nor needed — a recipe that worked by luck rather than by expression.
3427
3467
  */
3428
3468
  documents = {
3429
- /** Same filters as the end-user client's `documents.list`, so the two agree. */
3469
+ /**
3470
+ * One PAGE of the corpus — the same envelope, the same filters and the same paging as
3471
+ * the end-user client's `documents.list`, because they are the same route.
3472
+ *
3473
+ * The route became a page in OBE-190 and this declaration did not move with it: it
3474
+ * still said `Promise<DocumentOut[]>`, so a TypeScript caller wrote `.length` and got
3475
+ * `undefined` with no type error and no exception — a census that counted documents
3476
+ * reported zero. That is OBE-147 verbatim, one collection over, and the docstring
3477
+ * describing it sits sixty lines above this one (OBE-195). The query type had the
3478
+ * mirror-image gap: `limit`/`offset` worked at runtime and were unwritable in
3479
+ * TypeScript, so the documented paging loop did not compile against the client that
3480
+ * recommends it.
3481
+ *
3482
+ * `with_total` adds a `total`; it is what "how big is this corpus" is asked with, and
3483
+ * it costs a second query. "Is there another page" is `has_more`, and it is free.
3484
+ */
3430
3485
  list: (query = {}) => {
3431
- const qs = new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)).toString();
3486
+ const qs = new URLSearchParams(Object.entries(query)
3487
+ .filter(([, v]) => v != null)
3488
+ .map(([k, v]) => [k, String(v)])).toString();
3432
3489
  return this.request("GET", `/documents${qs ? `?${qs}` : ""}`);
3433
3490
  },
3434
3491
  get: (documentId) => this.request("GET", `/documents/${documentId}`),
@@ -3473,8 +3530,7 @@ export class OberikProject {
3473
3530
  */
3474
3531
  opts = {}) => {
3475
3532
  const form = new FormData();
3476
- const name = opts.filename ?? file.name ?? "upload.bin";
3477
- form.append("file", asBlob(file), name);
3533
+ form.append("file", asBlob(file), uploadName(file, opts.filename));
3478
3534
  form.append("tags", (opts.tags ?? []).join(","));
3479
3535
  if (opts.visibility)
3480
3536
  form.append("visibility", opts.visibility);