@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/cjs/index.js CHANGED
@@ -773,6 +773,25 @@ function asBlob(input) {
773
773
  // handing that straight to Blob can carry bytes belonging to something else.
774
774
  return new Blob([bytes.slice()]);
775
775
  }
776
+ /**
777
+ * The name a document is stored under: what the caller passed, the file's own, or a
778
+ * placeholder.
779
+ *
780
+ * One function for both clients, because they disagreed about it. The project client
781
+ * derived a name and fell back to `"upload.bin"`; the end-user client took `filename` in a
782
+ * **required** options object, so a JavaScript caller writing the documented
783
+ * `uploadAndWait(file)` got `TypeError: Cannot read properties of undefined (reading
784
+ * 'filename')` — thrown from inside the SDK, with nothing in the stack naming Oberik, at
785
+ * the reader most likely to have copied the snippet (OBE-195).
786
+ *
787
+ * Both are optional now and both land here. `"upload.bin"` rather than a refusal: a `File`
788
+ * carries its name and a Blob, Buffer or ArrayBuffer does not, and an upload that arrives
789
+ * with a placeholder name is visibly wrong in the listing, where a caller can fix it with
790
+ * `documents.update`. Pass `filename` — it is what the document is listed and cited under.
791
+ */
792
+ function uploadName(file, filename) {
793
+ return filename || file?.name || "upload.bin";
794
+ }
776
795
  /** Normalise whatever an `onQuestion` handler returned into an answer the API accepts.
777
796
  *
778
797
  * A handler returning a bare string was silently ignored. `{ ...answer }` on a string
@@ -2361,17 +2380,35 @@ class AgentFramework {
2361
2380
  },
2362
2381
  };
2363
2382
  documents = {
2383
+ /**
2384
+ * One PAGE of documents, newest first — `{ items, has_more, next_offset }`.
2385
+ *
2386
+ * It was a bare array, which asserts completeness by omission, on the one collection
2387
+ * that only grows: a live project of 302 documents returned all 302 and 181KB on every
2388
+ * call, and a 10k corpus would be ~6MB on a route a dashboard opens with (OBE-190).
2389
+ * `limit` defaults to 100 and is capped at 500; page with `next_offset`, which is given
2390
+ * rather than left as arithmetic because the cap can make `offset + limit` wrong.
2391
+ *
2392
+ * `with_total` adds a `total` and costs a second query — for a summary, not for paging.
2393
+ * "Is there another page" is `has_more`, and it is free.
2394
+ */
2364
2395
  list: (query = {}) => this.request("GET", "/documents", { query }),
2365
2396
  get: (id) => this.request("GET", `/documents/${id}`),
2366
2397
  delete: (id) => this.request("DELETE", `/documents/${id}`),
2367
2398
  retrieve: (body) => this.request("POST", "/documents/retrieve", { body }),
2368
2399
  /** Small-file convenience upload via multipart form (server proxies to S3). */
2369
- uploadSimple: async (file, opts) => {
2400
+ uploadSimple: async (file,
2401
+ // Optional, and the same shape the project client takes: this signature required an
2402
+ // options object, so `uploadSimple(file)` and `uploadAndWait(file)` — the way the
2403
+ // docs write them — threw a TypeError out of the SDK's own body instead of saying
2404
+ // what was missing (OBE-195).
2405
+ opts = {}) => {
2406
+ const filename = uploadName(file, opts.filename);
2370
2407
  const form = new FormData();
2371
2408
  const blob = typeof Blob !== "undefined" && file instanceof Blob
2372
2409
  ? file
2373
2410
  : new Blob([file], { type: opts.contentType ?? "application/octet-stream" });
2374
- form.append("file", blob, opts.filename);
2411
+ form.append("file", blob, filename);
2375
2412
  form.append("tags", (opts.tags ?? []).join(","));
2376
2413
  // Set visibility AT UPLOAD, not after: a document is retrievable as soon as
2377
2414
  // ingestion finishes, so patching later leaves a window where it's readable by
@@ -2404,7 +2441,7 @@ class AgentFramework {
2404
2441
  return (await res.json());
2405
2442
  },
2406
2443
  /** Resumable presigned multipart upload (direct to S3). */
2407
- upload: (file, opts) => this.multipartUpload(file, opts),
2444
+ upload: (file, opts = {}) => this.multipartUpload(file, opts),
2408
2445
  /** Poll a document until ingestion finishes (status "ready" or "failed").
2409
2446
  * Ingestion is async, so querying a just-uploaded doc may return nothing until
2410
2447
  * this resolves. Throws on "failed" or timeout. */
@@ -2428,7 +2465,7 @@ class AgentFramework {
2428
2465
  // Takes the ACL fields too: this is the upload most callers use, so omitting them
2429
2466
  // here forced a second `patch` and left a window where the document was already
2430
2467
  // retrievable at its default visibility.
2431
- opts) => {
2468
+ opts = {}) => {
2432
2469
  const doc = await this.documents.uploadSimple(file, opts);
2433
2470
  return this.documents.waitReady(doc.id, { timeoutMs: opts.timeoutMs, signal: opts.signal });
2434
2471
  },
@@ -3089,7 +3126,7 @@ class AgentFramework {
3089
3126
  const maxRetries = opts.maxRetries ?? 5;
3090
3127
  const presign = await this.request("POST", "/documents/presign-upload", {
3091
3128
  body: {
3092
- filename: opts.filename,
3129
+ filename: uploadName(file, opts.filename),
3093
3130
  content_type: opts.contentType,
3094
3131
  tags: opts.tags ?? [],
3095
3132
  ...(opts.visibility ? { visibility: opts.visibility } : {}),
@@ -3369,6 +3406,9 @@ class OberikProject {
3369
3406
  * turning citation markers off, meant dropping to raw HTTP through `raw()`.
3370
3407
  */
3371
3408
  project = {
3409
+ /** The project, its capability ceiling and its settings — plus `readiness` and the
3410
+ * `health` / `healthDetail` badge, so "is this project working" is answered by the
3411
+ * first thing you fetch rather than one route further on. */
3372
3412
  get: () => this.request("GET", ""),
3373
3413
  /** Merges — send only what you want to change. */
3374
3414
  set: (fields) => this.request("PATCH", "", fields),
@@ -3442,9 +3482,26 @@ class OberikProject {
3442
3482
  * accepted nor needed — a recipe that worked by luck rather than by expression.
3443
3483
  */
3444
3484
  documents = {
3445
- /** Same filters as the end-user client's `documents.list`, so the two agree. */
3485
+ /**
3486
+ * One PAGE of the corpus — the same envelope, the same filters and the same paging as
3487
+ * the end-user client's `documents.list`, because they are the same route.
3488
+ *
3489
+ * The route became a page in OBE-190 and this declaration did not move with it: it
3490
+ * still said `Promise<DocumentOut[]>`, so a TypeScript caller wrote `.length` and got
3491
+ * `undefined` with no type error and no exception — a census that counted documents
3492
+ * reported zero. That is OBE-147 verbatim, one collection over, and the docstring
3493
+ * describing it sits sixty lines above this one (OBE-195). The query type had the
3494
+ * mirror-image gap: `limit`/`offset` worked at runtime and were unwritable in
3495
+ * TypeScript, so the documented paging loop did not compile against the client that
3496
+ * recommends it.
3497
+ *
3498
+ * `with_total` adds a `total`; it is what "how big is this corpus" is asked with, and
3499
+ * it costs a second query. "Is there another page" is `has_more`, and it is free.
3500
+ */
3446
3501
  list: (query = {}) => {
3447
- const qs = new URLSearchParams(Object.entries(query).filter(([, v]) => v != null)).toString();
3502
+ const qs = new URLSearchParams(Object.entries(query)
3503
+ .filter(([, v]) => v != null)
3504
+ .map(([k, v]) => [k, String(v)])).toString();
3448
3505
  return this.request("GET", `/documents${qs ? `?${qs}` : ""}`);
3449
3506
  },
3450
3507
  get: (documentId) => this.request("GET", `/documents/${documentId}`),
@@ -3489,8 +3546,7 @@ class OberikProject {
3489
3546
  */
3490
3547
  opts = {}) => {
3491
3548
  const form = new FormData();
3492
- const name = opts.filename ?? file.name ?? "upload.bin";
3493
- form.append("file", asBlob(file), name);
3549
+ form.append("file", asBlob(file), uploadName(file, opts.filename));
3494
3550
  form.append("tags", (opts.tags ?? []).join(","));
3495
3551
  if (opts.visibility)
3496
3552
  form.append("visibility", opts.visibility);