@babav/knowledge-core-client 0.32.0 → 0.33.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
@@ -94,30 +94,37 @@ export interface QueryRequest {
94
94
  * "free" = renderer's judgment / not a declared arrangement (also the value for non-conceptual
95
95
  * registers). */
96
96
  export type VisualLayout = "linear-flow" | "ring-cycle" | "nesting" | "radial-hub" | "side-by-side" | "stacked-layers" | "two-state" | "free";
97
- /** A visual attached to a response, anchored by char offsets into `answer` (SAME coordinate
98
- * system as citations).
97
+ /** A visual attached to a response.
99
98
  *
100
99
  * IDs-ONLY: the API never returns a URL. Every visual (chart / structural / conceptual / scenic) is
101
- * a PNG served by KC and identified by its `id`. To display it, fetch the bytes by id:
102
- * `getVisualImage(visual.id)` (or GET /v1/visuals/{id}/image with your API key). `image` carries only
103
- * the pixel DIMENSIONS, for layout. The image is authenticated + tenant-scoped exactly like every
104
- * other KC call; there is no URL, no signed GCS link, and nothing to expire. */
100
+ * a PNG served by KC and identified by its `id`. To display it, fetch the bytes by id
101
+ * `getVisualImage(visual.id)` for one, or `getVisualImages([...ids])` to fetch a whole answer's
102
+ * visuals in a SINGLE call (no per-visual fan-out). The image is authenticated + tenant-scoped
103
+ * exactly like every other KC call; there is no URL, no signed GCS link. */
105
104
  export interface Visual {
106
105
  id: string;
107
- anchor: {
106
+ /** Char offsets into `answer` (SAME coordinate system as citations). OPTIONAL: a visual may be
107
+ * anchorless (belongs in the trailing strip) — when absent, place it after the answer. */
108
+ anchor?: {
108
109
  start: number;
109
110
  end: number;
110
111
  };
111
112
  register: "chart" | "structural" | "conceptual" | "scenic";
112
- priority: number;
113
+ /** Client display ordering (higher first). OPTIONAL — default to 0 / arrival order if absent. */
114
+ priority?: number;
113
115
  payload: Record<string, unknown>;
114
116
  /** Declared spatial arrangement (meaningful for `conceptual`; "free" otherwise). */
115
117
  layout: VisualLayout;
116
- /** Pixel dimensions of the PNG (for layout). Fetch the bytes with getVisualImage(id). */
117
- image?: {
118
+ /** Pixel dimensions of the PNG ALWAYS present on a shipped visual, so the UI can reserve space
119
+ * before the bytes arrive (no layout shift). Fetch the bytes with getVisualImage(id). */
120
+ image: {
118
121
  width: number;
119
122
  height: number;
120
123
  };
124
+ /** Short KC-authored figure caption (the idea the visual carries). Present when authored. */
125
+ caption?: string;
126
+ /** Plain-text description for screen readers / accessible PDF·DOCX·LaTeX export. Present when authored. */
127
+ alt?: string;
121
128
  }
122
129
  /** Visual stage (streaming): generation has started. MAY be emitted twice — first with
123
130
  * `pending: null` (started, count unknown), then again with the ACCURATE post-vet count before
@@ -126,8 +133,24 @@ export interface VisualsPending {
126
133
  pending: number | null;
127
134
  }
128
135
  export interface VisualsMeta {
136
+ /** Visuals that shipped (authoritative — always matches the number of `visual` events). */
129
137
  count: number;
138
+ /** Concepts accepted by the vet but not shipped (render_failed + timeout). */
130
139
  dropped: number;
140
+ /** Counts-by-cause for every PROPOSED concept that did not become a visible visual, so a missing
141
+ * visual is explainable rather than silent. Keys (zero causes omitted):
142
+ * - `declined` the vet declined/trimmed the concept (never promised; reduces the pending count)
143
+ * - `render_failed` accepted but authoring/rendering/vision-review dropped it
144
+ * - `timeout` accepted but the post-answer render budget cut it off
145
+ * `dropped` = render_failed + timeout; `declined` is extra context beyond `dropped`. */
146
+ dropped_reasons?: Record<string, number>;
147
+ }
148
+ /** One image returned by getVisualImages(). `blob` is the PNG (browser: `URL.createObjectURL(blob)`). */
149
+ export interface VisualImage {
150
+ id: string;
151
+ blob: Blob;
152
+ width: number;
153
+ height: number;
131
154
  }
132
155
  export interface QueryResponse {
133
156
  conversation_id: UUID | null;
@@ -540,6 +563,15 @@ export declare class KnowledgeCoreClient extends HttpBase {
540
563
  * response / streamed `visual` event / persisted message. Returns a Blob (browser: `URL.
541
564
  * createObjectURL(blob)` for an <img>; Node: `Buffer.from(await blob.arrayBuffer())`). */
542
565
  getVisualImage(visualId: UUID | string, signal?: AbortSignal): Promise<Blob>;
566
+ /** Fetch MANY visual PNGs in ONE call — the way to load all of an answer's (or a conversation's)
567
+ * visuals without a per-visual round trip. Pass up to 32 ids; over that, split into chunks (the
568
+ * API returns 400 `too_many_ids`). Returns the found images (as Blobs, ready for
569
+ * `URL.createObjectURL`) plus `missing` — ids with no stored image (reaped/expired), which the UI
570
+ * can show as "visual no longer available". Order of `images` is not guaranteed; key by `id`. */
571
+ getVisualImages(visualIds: Array<UUID | string>, signal?: AbortSignal): Promise<{
572
+ images: VisualImage[];
573
+ missing: string[];
574
+ }>;
543
575
  /** DUMMY-PROOF CHAT. Every message goes through a conversation — it is structurally impossible
544
576
  * to send a chat turn as a non-persisted one-shot. Use this for ANY chat UI. Use the low-level
545
577
  * `query`/`queryStream` ONLY for programmatic one-shots (tools).
package/dist/index.js CHANGED
@@ -190,6 +190,21 @@ export class KnowledgeCoreClient extends HttpBase {
190
190
  }
191
191
  return await res.blob();
192
192
  }
193
+ /** Fetch MANY visual PNGs in ONE call — the way to load all of an answer's (or a conversation's)
194
+ * visuals without a per-visual round trip. Pass up to 32 ids; over that, split into chunks (the
195
+ * API returns 400 `too_many_ids`). Returns the found images (as Blobs, ready for
196
+ * `URL.createObjectURL`) plus `missing` — ids with no stored image (reaped/expired), which the UI
197
+ * can show as "visual no longer available". Order of `images` is not guaranteed; key by `id`. */
198
+ async getVisualImages(visualIds, signal) {
199
+ if (visualIds.length === 0)
200
+ return { images: [], missing: [] };
201
+ const r = await this.request("POST", "/v1/visuals/images", { json: { ids: visualIds }, signal });
202
+ const images = r.images.map((im) => {
203
+ const bytes = Uint8Array.from(atob(im.png_base64), (c) => c.charCodeAt(0));
204
+ return { id: im.id, blob: new Blob([bytes], { type: "image/png" }), width: im.width, height: im.height };
205
+ });
206
+ return { images, missing: r.missing ?? [] };
207
+ }
193
208
  /** DUMMY-PROOF CHAT. Every message goes through a conversation — it is structurally impossible
194
209
  * to send a chat turn as a non-persisted one-shot. Use this for ANY chat UI. Use the low-level
195
210
  * `query`/`queryStream` ONLY for programmatic one-shots (tools).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@babav/knowledge-core-client",
3
- "version": "0.32.0",
3
+ "version": "0.33.0",
4
4
  "description": "TypeScript client for the Babav Knowledge Core API (Deno + Node 18+, zero deps). Includes the babav.visual grammar TYPES at the ./visual subpath (types only; all visual rendering is server-side).",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
package/src/index.ts CHANGED
@@ -128,24 +128,31 @@ export type VisualLayout =
128
128
  | "two-state"
129
129
  | "free";
130
130
 
131
- /** A visual attached to a response, anchored by char offsets into `answer` (SAME coordinate
132
- * system as citations).
131
+ /** A visual attached to a response.
133
132
  *
134
133
  * IDs-ONLY: the API never returns a URL. Every visual (chart / structural / conceptual / scenic) is
135
- * a PNG served by KC and identified by its `id`. To display it, fetch the bytes by id:
136
- * `getVisualImage(visual.id)` (or GET /v1/visuals/{id}/image with your API key). `image` carries only
137
- * the pixel DIMENSIONS, for layout. The image is authenticated + tenant-scoped exactly like every
138
- * other KC call; there is no URL, no signed GCS link, and nothing to expire. */
134
+ * a PNG served by KC and identified by its `id`. To display it, fetch the bytes by id
135
+ * `getVisualImage(visual.id)` for one, or `getVisualImages([...ids])` to fetch a whole answer's
136
+ * visuals in a SINGLE call (no per-visual fan-out). The image is authenticated + tenant-scoped
137
+ * exactly like every other KC call; there is no URL, no signed GCS link. */
139
138
  export interface Visual {
140
139
  id: string;
141
- anchor: { start: number; end: number };
140
+ /** Char offsets into `answer` (SAME coordinate system as citations). OPTIONAL: a visual may be
141
+ * anchorless (belongs in the trailing strip) — when absent, place it after the answer. */
142
+ anchor?: { start: number; end: number };
142
143
  register: "chart" | "structural" | "conceptual" | "scenic";
143
- priority: number;
144
+ /** Client display ordering (higher first). OPTIONAL — default to 0 / arrival order if absent. */
145
+ priority?: number;
144
146
  payload: Record<string, unknown>; // {kind: "grammar"|"vega_lite"|"image", ...}
145
147
  /** Declared spatial arrangement (meaningful for `conceptual`; "free" otherwise). */
146
148
  layout: VisualLayout;
147
- /** Pixel dimensions of the PNG (for layout). Fetch the bytes with getVisualImage(id). */
148
- image?: { width: number; height: number };
149
+ /** Pixel dimensions of the PNG ALWAYS present on a shipped visual, so the UI can reserve space
150
+ * before the bytes arrive (no layout shift). Fetch the bytes with getVisualImage(id). */
151
+ image: { width: number; height: number };
152
+ /** Short KC-authored figure caption (the idea the visual carries). Present when authored. */
153
+ caption?: string;
154
+ /** Plain-text description for screen readers / accessible PDF·DOCX·LaTeX export. Present when authored. */
155
+ alt?: string;
149
156
  }
150
157
 
151
158
  /** Visual stage (streaming): generation has started. MAY be emitted twice — first with
@@ -156,8 +163,25 @@ export interface VisualsPending {
156
163
  }
157
164
 
158
165
  export interface VisualsMeta {
166
+ /** Visuals that shipped (authoritative — always matches the number of `visual` events). */
159
167
  count: number;
168
+ /** Concepts accepted by the vet but not shipped (render_failed + timeout). */
160
169
  dropped: number;
170
+ /** Counts-by-cause for every PROPOSED concept that did not become a visible visual, so a missing
171
+ * visual is explainable rather than silent. Keys (zero causes omitted):
172
+ * - `declined` the vet declined/trimmed the concept (never promised; reduces the pending count)
173
+ * - `render_failed` accepted but authoring/rendering/vision-review dropped it
174
+ * - `timeout` accepted but the post-answer render budget cut it off
175
+ * `dropped` = render_failed + timeout; `declined` is extra context beyond `dropped`. */
176
+ dropped_reasons?: Record<string, number>;
177
+ }
178
+
179
+ /** One image returned by getVisualImages(). `blob` is the PNG (browser: `URL.createObjectURL(blob)`). */
180
+ export interface VisualImage {
181
+ id: string;
182
+ blob: Blob;
183
+ width: number;
184
+ height: number;
161
185
  }
162
186
 
163
187
  export interface QueryResponse {
@@ -355,7 +379,7 @@ export interface Message {
355
379
  groundedness: Record<string, unknown> | null;
356
380
  retrieval_contents: unknown[] | null;
357
381
  usage: Record<string, unknown> | null;
358
- visuals?: { items: Visual[]; meta: VisualsMeta } | null; // post-generation visuals (re-signed URLs on read)
382
+ visuals?: { items: Visual[]; meta: VisualsMeta } | null; // persisted visuals; fetch bytes by id (getVisualImages)
359
383
  }
360
384
  export interface Attachment {
361
385
  id: UUID;
@@ -664,6 +688,26 @@ export class KnowledgeCoreClient extends HttpBase {
664
688
  return await res.blob();
665
689
  }
666
690
 
691
+ /** Fetch MANY visual PNGs in ONE call — the way to load all of an answer's (or a conversation's)
692
+ * visuals without a per-visual round trip. Pass up to 32 ids; over that, split into chunks (the
693
+ * API returns 400 `too_many_ids`). Returns the found images (as Blobs, ready for
694
+ * `URL.createObjectURL`) plus `missing` — ids with no stored image (reaped/expired), which the UI
695
+ * can show as "visual no longer available". Order of `images` is not guaranteed; key by `id`. */
696
+ async getVisualImages(
697
+ visualIds: Array<UUID | string>,
698
+ signal?: AbortSignal,
699
+ ): Promise<{ images: VisualImage[]; missing: string[] }> {
700
+ if (visualIds.length === 0) return { images: [], missing: [] };
701
+ const r = await this.request<{ images: Array<{ id: string; png_base64: string; width: number; height: number }>; missing: string[] }>(
702
+ "POST", "/v1/visuals/images", { json: { ids: visualIds }, signal },
703
+ );
704
+ const images = r.images.map((im) => {
705
+ const bytes = Uint8Array.from(atob(im.png_base64), (c) => c.charCodeAt(0));
706
+ return { id: im.id, blob: new Blob([bytes], { type: "image/png" }), width: im.width, height: im.height };
707
+ });
708
+ return { images, missing: r.missing ?? [] };
709
+ }
710
+
667
711
  /** DUMMY-PROOF CHAT. Every message goes through a conversation — it is structurally impossible
668
712
  * to send a chat turn as a non-persisted one-shot. Use this for ANY chat UI. Use the low-level
669
713
  * `query`/`queryStream` ONLY for programmatic one-shots (tools).