@rei-standard/amsg-shared 0.1.0-next.3 → 0.1.0-next.4

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.cjs CHANGED
@@ -22,15 +22,18 @@ __export(src_exports, {
22
22
  MESSAGE_KIND: () => MESSAGE_KIND,
23
23
  MESSAGE_TYPE: () => MESSAGE_TYPE,
24
24
  PUSH_SOURCE: () => PUSH_SOURCE,
25
+ base64UrlToBytes: () => base64UrlToBytes,
25
26
  buildContentPush: () => buildContentPush,
26
27
  buildErrorPush: () => buildErrorPush,
27
28
  buildReasoningPush: () => buildReasoningPush,
28
29
  buildToolRequestPush: () => buildToolRequestPush,
29
30
  chunkReasoningByUtf8Bytes: () => chunkReasoningByUtf8Bytes,
31
+ concatBytes: () => concatBytes,
30
32
  isContentPush: () => isContentPush,
31
33
  isErrorPush: () => isErrorPush,
32
34
  isReasoningPush: () => isReasoningPush,
33
- isToolRequestPush: () => isToolRequestPush
35
+ isToolRequestPush: () => isToolRequestPush,
36
+ toUint8: () => toUint8
34
37
  });
35
38
  module.exports = __toCommonJS(src_exports);
36
39
  var MESSAGE_KIND = Object.freeze({
@@ -91,6 +94,7 @@ function buildReasoningPush(args) {
91
94
  if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
92
95
  throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
93
96
  }
97
+ validateNotificationArg("ReasoningPush", args.notification);
94
98
  const push = {
95
99
  messageKind: "reasoning",
96
100
  messageType: args.messageType,
@@ -109,6 +113,7 @@ function buildReasoningPush(args) {
109
113
  if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
110
114
  if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
111
115
  if (args.metadata !== void 0) push.metadata = args.metadata;
116
+ if (args.notification !== void 0) push.notification = args.notification;
112
117
  return push;
113
118
  }
114
119
  function buildToolRequestPush(args) {
@@ -146,6 +151,9 @@ function validateNotificationArg(kind, value) {
146
151
  /** @type {Record<string, unknown>} */
147
152
  value
148
153
  );
154
+ if (n.show !== void 0 && !["auto", "always", "when-hidden", false].includes(n.show)) {
155
+ throw new Error(`[amsg-shared] ${kind}: 'notification.show' must be "auto", "always", "when-hidden", or false`);
156
+ }
149
157
  for (const f of ["title", "body", "icon", "badge", "tag"]) {
150
158
  if (n[f] !== void 0 && typeof n[f] !== "string") {
151
159
  throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
@@ -156,6 +164,9 @@ function validateNotificationArg(kind, value) {
156
164
  throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
157
165
  }
158
166
  }
167
+ if (n.data !== void 0 && (n.data === null || typeof n.data !== "object" || Array.isArray(n.data))) {
168
+ throw new Error(`[amsg-shared] ${kind}: 'notification.data' must be a plain object when present`);
169
+ }
159
170
  }
160
171
  function buildErrorPush(args) {
161
172
  requireField("ErrorPush", "messageType", args.messageType);
@@ -166,6 +177,7 @@ function buildErrorPush(args) {
166
177
  if (typeof args.message !== "string") {
167
178
  throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
168
179
  }
180
+ validateNotificationArg("ErrorPush", args.notification);
169
181
  const push = {
170
182
  messageKind: "error",
171
183
  messageType: args.messageType,
@@ -179,6 +191,7 @@ function buildErrorPush(args) {
179
191
  if (args.iteration !== void 0) push.iteration = args.iteration;
180
192
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
181
193
  if (args.metadata !== void 0) push.metadata = args.metadata;
194
+ if (args.notification !== void 0) push.notification = args.notification;
182
195
  return push;
183
196
  }
184
197
  function isContentPush(value) {
@@ -225,3 +238,29 @@ function chunkReasoningByUtf8Bytes(text, maxBytes) {
225
238
  }
226
239
  return chunks;
227
240
  }
241
+ function toUint8(buf) {
242
+ if (buf instanceof Uint8Array) return buf;
243
+ if (buf instanceof ArrayBuffer) return new Uint8Array(buf);
244
+ if (ArrayBuffer.isView(buf)) return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
245
+ throw new TypeError("Expected ArrayBuffer / Uint8Array");
246
+ }
247
+ function base64UrlToBytes(input) {
248
+ const s = String(input).replace(/-/g, "+").replace(/_/g, "/");
249
+ const pad = (4 - s.length % 4) % 4;
250
+ const padded = s + "=".repeat(pad);
251
+ const bin = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("binary");
252
+ const out = new Uint8Array(bin.length);
253
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
254
+ return out;
255
+ }
256
+ function concatBytes(...chunks) {
257
+ let total = 0;
258
+ for (const c of chunks) total += c.byteLength;
259
+ const out = new Uint8Array(total);
260
+ let offset = 0;
261
+ for (const c of chunks) {
262
+ out.set(c instanceof Uint8Array ? c : new Uint8Array(c.buffer || c), offset);
263
+ offset += c.byteLength;
264
+ }
265
+ return out;
266
+ }
package/dist/index.d.cts CHANGED
@@ -153,6 +153,7 @@ export function buildToolRequestPush(args: {
153
153
  * @param {number} [args.iteration]
154
154
  * @param {string} [args.messageSubtype]
155
155
  * @param {Object} [args.metadata]
156
+ * @param {NotificationDirective} [args.notification]
156
157
  * @returns {ErrorPush}
157
158
  */
158
159
  export function buildErrorPush(args: {
@@ -166,6 +167,7 @@ export function buildErrorPush(args: {
166
167
  iteration?: number;
167
168
  messageSubtype?: string;
168
169
  metadata?: any;
170
+ notification?: NotificationDirective;
169
171
  }): ErrorPush;
170
172
  /**
171
173
  * Type guard: returns true if the argument is a {@link ContentPush}.
@@ -229,6 +231,22 @@ export function isErrorPush(value: unknown): value is ErrorPush;
229
231
  * @returns {string[]}
230
232
  */
231
233
  export function chunkReasoningByUtf8Bytes(text: string, maxBytes: number): string[];
234
+ /**
235
+ * Coerce ArrayBuffer | Uint8Array | view → Uint8Array (no copy when possible).
236
+ */
237
+ export function toUint8(buf: any): Uint8Array<ArrayBufferLike>;
238
+ /**
239
+ * Decode base64url (with or without padding) → Uint8Array.
240
+ * @param {string} input
241
+ * @returns {Uint8Array}
242
+ */
243
+ export function base64UrlToBytes(input: string): Uint8Array;
244
+ /**
245
+ * Concatenate Uint8Arrays into a single Uint8Array.
246
+ * @param {...(Uint8Array | ArrayBuffer | ArrayBufferView)} chunks
247
+ * @returns {Uint8Array}
248
+ */
249
+ export function concatBytes(...chunks: (Uint8Array | ArrayBuffer | ArrayBufferView)[]): Uint8Array;
232
250
  /**
233
251
  * @rei-standard/amsg-shared
234
252
  *
@@ -336,30 +354,31 @@ export type AmsgPushCommon = {
336
354
  * - Caller passthrough. Packages MUST NOT write here.
337
355
  */
338
356
  metadata?: any;
357
+ /**
358
+ * - SW notification strategy.
359
+ */
360
+ notification?: NotificationDirective;
339
361
  };
340
362
  /**
341
- * SW-rendering directive carried on `ContentPush` / `ToolRequestPush`.
342
- * Mirrors the seven fields that `amsg-sw`'s `createNotificationFromPayload`
343
- * actually consumes (`notification.{title,body,icon,badge,tag,renotify,requireInteraction}`)
344
- * — typing all seven (rather than just `title` / `body`) so callers
345
- * don't lose IDE checking on the other five and slip back into the
346
- * untyped-spread footgun this typedef was added to close.
363
+ * SW-rendering directive. Mirrors the fields that `amsg-sw`'s
364
+ * `createNotificationFromPayload` consumes (`notification.{title,body,icon,badge,tag,renotify,requireInteraction,data}`)
365
+ * — typing all fields so callers don't lose IDE checking and slip back into the untyped-spread footgun.
347
366
  *
348
367
  * Routing in SW (kept here so producers don't have to cross-check):
349
- * - `messageKind: 'content'` (and legacy un-kinded payloads) →
350
- * `notification.*` is consulted, with per-field fallback to
368
+ * - By default (`show: "auto"` or omitted), `messageKind: 'content'` (and legacy un-kinded payloads)
369
+ * will display a system notification. `reasoning` / `tool_request` / `error` will dispatch silently.
370
+ * - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
371
+ * - When rendering, `notification.*` is consulted, with per-field fallback to
351
372
  * the top-level `title` / `avatarUrl` / `messageId` and finally
352
373
  * to the SW's `defaultIcon` / `defaultBadge` options. Everything
353
374
  * else (`tag`, `renotify`, `requireInteraction`) has no top-level
354
- * fallback — set them under `notification` or accept the SW
355
- * default (`messageId`-derived tag, no renotify, no requireInteraction).
356
- * - `messageKind: 'reasoning'` / `'tool_request'` / `'error'` →
357
- * dispatched silently to controlled clients. `notification` is
358
- * ignored. (It's still typed on `ToolRequestPush` because the
359
- * splitter demotes prefix chunks to `messageKind: 'content'`, at
360
- * which point the field starts mattering.)
375
+ * fallback — set them under `notification` or accept the SW default.
361
376
  */
362
377
  export type NotificationDirective = {
378
+ /**
379
+ * - Rendering strategy. Defaults to "auto" (render only if messageKind is content).
380
+ */
381
+ show?: "auto" | "always" | "when-hidden" | false;
363
382
  /**
364
383
  * - Notification title override.
365
384
  */
@@ -388,6 +407,10 @@ export type NotificationDirective = {
388
407
  * - Notification stays until user dismisses. Default false at SW.
389
408
  */
390
409
  requireInteraction?: boolean;
410
+ /**
411
+ * - Custom payload data to attach to the notification.
412
+ */
413
+ data?: Record<string, unknown>;
391
414
  };
392
415
  /**
393
416
  * Final user-facing content. Sentence-split bursts of N use
@@ -403,7 +426,6 @@ export type ContentPush = AmsgPushCommon & {
403
426
  messageIndex?: number;
404
427
  totalMessages?: number;
405
428
  taskId?: string | null;
406
- notification?: NotificationDirective;
407
429
  };
408
430
  /**
409
431
  * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
@@ -455,7 +477,6 @@ export type ToolRequestPush = AmsgPushCommon & {
455
477
  title?: string;
456
478
  contactName?: string;
457
479
  message?: string;
458
- notification?: NotificationDirective;
459
480
  };
460
481
  /**
461
482
  * Producer-level error. Replaces the legacy
package/dist/index.d.ts CHANGED
@@ -153,6 +153,7 @@ export function buildToolRequestPush(args: {
153
153
  * @param {number} [args.iteration]
154
154
  * @param {string} [args.messageSubtype]
155
155
  * @param {Object} [args.metadata]
156
+ * @param {NotificationDirective} [args.notification]
156
157
  * @returns {ErrorPush}
157
158
  */
158
159
  export function buildErrorPush(args: {
@@ -166,6 +167,7 @@ export function buildErrorPush(args: {
166
167
  iteration?: number;
167
168
  messageSubtype?: string;
168
169
  metadata?: any;
170
+ notification?: NotificationDirective;
169
171
  }): ErrorPush;
170
172
  /**
171
173
  * Type guard: returns true if the argument is a {@link ContentPush}.
@@ -229,6 +231,22 @@ export function isErrorPush(value: unknown): value is ErrorPush;
229
231
  * @returns {string[]}
230
232
  */
231
233
  export function chunkReasoningByUtf8Bytes(text: string, maxBytes: number): string[];
234
+ /**
235
+ * Coerce ArrayBuffer | Uint8Array | view → Uint8Array (no copy when possible).
236
+ */
237
+ export function toUint8(buf: any): Uint8Array<ArrayBufferLike>;
238
+ /**
239
+ * Decode base64url (with or without padding) → Uint8Array.
240
+ * @param {string} input
241
+ * @returns {Uint8Array}
242
+ */
243
+ export function base64UrlToBytes(input: string): Uint8Array;
244
+ /**
245
+ * Concatenate Uint8Arrays into a single Uint8Array.
246
+ * @param {...(Uint8Array | ArrayBuffer | ArrayBufferView)} chunks
247
+ * @returns {Uint8Array}
248
+ */
249
+ export function concatBytes(...chunks: (Uint8Array | ArrayBuffer | ArrayBufferView)[]): Uint8Array;
232
250
  /**
233
251
  * @rei-standard/amsg-shared
234
252
  *
@@ -336,30 +354,31 @@ export type AmsgPushCommon = {
336
354
  * - Caller passthrough. Packages MUST NOT write here.
337
355
  */
338
356
  metadata?: any;
357
+ /**
358
+ * - SW notification strategy.
359
+ */
360
+ notification?: NotificationDirective;
339
361
  };
340
362
  /**
341
- * SW-rendering directive carried on `ContentPush` / `ToolRequestPush`.
342
- * Mirrors the seven fields that `amsg-sw`'s `createNotificationFromPayload`
343
- * actually consumes (`notification.{title,body,icon,badge,tag,renotify,requireInteraction}`)
344
- * — typing all seven (rather than just `title` / `body`) so callers
345
- * don't lose IDE checking on the other five and slip back into the
346
- * untyped-spread footgun this typedef was added to close.
363
+ * SW-rendering directive. Mirrors the fields that `amsg-sw`'s
364
+ * `createNotificationFromPayload` consumes (`notification.{title,body,icon,badge,tag,renotify,requireInteraction,data}`)
365
+ * — typing all fields so callers don't lose IDE checking and slip back into the untyped-spread footgun.
347
366
  *
348
367
  * Routing in SW (kept here so producers don't have to cross-check):
349
- * - `messageKind: 'content'` (and legacy un-kinded payloads) →
350
- * `notification.*` is consulted, with per-field fallback to
368
+ * - By default (`show: "auto"` or omitted), `messageKind: 'content'` (and legacy un-kinded payloads)
369
+ * will display a system notification. `reasoning` / `tool_request` / `error` will dispatch silently.
370
+ * - `show: "always"`, `"when-hidden"`, or `false` overrides this default.
371
+ * - When rendering, `notification.*` is consulted, with per-field fallback to
351
372
  * the top-level `title` / `avatarUrl` / `messageId` and finally
352
373
  * to the SW's `defaultIcon` / `defaultBadge` options. Everything
353
374
  * else (`tag`, `renotify`, `requireInteraction`) has no top-level
354
- * fallback — set them under `notification` or accept the SW
355
- * default (`messageId`-derived tag, no renotify, no requireInteraction).
356
- * - `messageKind: 'reasoning'` / `'tool_request'` / `'error'` →
357
- * dispatched silently to controlled clients. `notification` is
358
- * ignored. (It's still typed on `ToolRequestPush` because the
359
- * splitter demotes prefix chunks to `messageKind: 'content'`, at
360
- * which point the field starts mattering.)
375
+ * fallback — set them under `notification` or accept the SW default.
361
376
  */
362
377
  export type NotificationDirective = {
378
+ /**
379
+ * - Rendering strategy. Defaults to "auto" (render only if messageKind is content).
380
+ */
381
+ show?: "auto" | "always" | "when-hidden" | false;
363
382
  /**
364
383
  * - Notification title override.
365
384
  */
@@ -388,6 +407,10 @@ export type NotificationDirective = {
388
407
  * - Notification stays until user dismisses. Default false at SW.
389
408
  */
390
409
  requireInteraction?: boolean;
410
+ /**
411
+ * - Custom payload data to attach to the notification.
412
+ */
413
+ data?: Record<string, unknown>;
391
414
  };
392
415
  /**
393
416
  * Final user-facing content. Sentence-split bursts of N use
@@ -403,7 +426,6 @@ export type ContentPush = AmsgPushCommon & {
403
426
  messageIndex?: number;
404
427
  totalMessages?: number;
405
428
  taskId?: string | null;
406
- notification?: NotificationDirective;
407
429
  };
408
430
  /**
409
431
  * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
@@ -455,7 +477,6 @@ export type ToolRequestPush = AmsgPushCommon & {
455
477
  title?: string;
456
478
  contactName?: string;
457
479
  message?: string;
458
- notification?: NotificationDirective;
459
480
  };
460
481
  /**
461
482
  * Producer-level error. Replaces the legacy
package/dist/index.mjs CHANGED
@@ -57,6 +57,7 @@ function buildReasoningPush(args) {
57
57
  if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
58
58
  throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
59
59
  }
60
+ validateNotificationArg("ReasoningPush", args.notification);
60
61
  const push = {
61
62
  messageKind: "reasoning",
62
63
  messageType: args.messageType,
@@ -75,6 +76,7 @@ function buildReasoningPush(args) {
75
76
  if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
76
77
  if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
77
78
  if (args.metadata !== void 0) push.metadata = args.metadata;
79
+ if (args.notification !== void 0) push.notification = args.notification;
78
80
  return push;
79
81
  }
80
82
  function buildToolRequestPush(args) {
@@ -112,6 +114,9 @@ function validateNotificationArg(kind, value) {
112
114
  /** @type {Record<string, unknown>} */
113
115
  value
114
116
  );
117
+ if (n.show !== void 0 && !["auto", "always", "when-hidden", false].includes(n.show)) {
118
+ throw new Error(`[amsg-shared] ${kind}: 'notification.show' must be "auto", "always", "when-hidden", or false`);
119
+ }
115
120
  for (const f of ["title", "body", "icon", "badge", "tag"]) {
116
121
  if (n[f] !== void 0 && typeof n[f] !== "string") {
117
122
  throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
@@ -122,6 +127,9 @@ function validateNotificationArg(kind, value) {
122
127
  throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
123
128
  }
124
129
  }
130
+ if (n.data !== void 0 && (n.data === null || typeof n.data !== "object" || Array.isArray(n.data))) {
131
+ throw new Error(`[amsg-shared] ${kind}: 'notification.data' must be a plain object when present`);
132
+ }
125
133
  }
126
134
  function buildErrorPush(args) {
127
135
  requireField("ErrorPush", "messageType", args.messageType);
@@ -132,6 +140,7 @@ function buildErrorPush(args) {
132
140
  if (typeof args.message !== "string") {
133
141
  throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
134
142
  }
143
+ validateNotificationArg("ErrorPush", args.notification);
135
144
  const push = {
136
145
  messageKind: "error",
137
146
  messageType: args.messageType,
@@ -145,6 +154,7 @@ function buildErrorPush(args) {
145
154
  if (args.iteration !== void 0) push.iteration = args.iteration;
146
155
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
147
156
  if (args.metadata !== void 0) push.metadata = args.metadata;
157
+ if (args.notification !== void 0) push.notification = args.notification;
148
158
  return push;
149
159
  }
150
160
  function isContentPush(value) {
@@ -191,17 +201,46 @@ function chunkReasoningByUtf8Bytes(text, maxBytes) {
191
201
  }
192
202
  return chunks;
193
203
  }
204
+ function toUint8(buf) {
205
+ if (buf instanceof Uint8Array) return buf;
206
+ if (buf instanceof ArrayBuffer) return new Uint8Array(buf);
207
+ if (ArrayBuffer.isView(buf)) return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
208
+ throw new TypeError("Expected ArrayBuffer / Uint8Array");
209
+ }
210
+ function base64UrlToBytes(input) {
211
+ const s = String(input).replace(/-/g, "+").replace(/_/g, "/");
212
+ const pad = (4 - s.length % 4) % 4;
213
+ const padded = s + "=".repeat(pad);
214
+ const bin = typeof atob === "function" ? atob(padded) : Buffer.from(padded, "base64").toString("binary");
215
+ const out = new Uint8Array(bin.length);
216
+ for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);
217
+ return out;
218
+ }
219
+ function concatBytes(...chunks) {
220
+ let total = 0;
221
+ for (const c of chunks) total += c.byteLength;
222
+ const out = new Uint8Array(total);
223
+ let offset = 0;
224
+ for (const c of chunks) {
225
+ out.set(c instanceof Uint8Array ? c : new Uint8Array(c.buffer || c), offset);
226
+ offset += c.byteLength;
227
+ }
228
+ return out;
229
+ }
194
230
  export {
195
231
  MESSAGE_KIND,
196
232
  MESSAGE_TYPE,
197
233
  PUSH_SOURCE,
234
+ base64UrlToBytes,
198
235
  buildContentPush,
199
236
  buildErrorPush,
200
237
  buildReasoningPush,
201
238
  buildToolRequestPush,
202
239
  chunkReasoningByUtf8Bytes,
240
+ concatBytes,
203
241
  isContentPush,
204
242
  isErrorPush,
205
243
  isReasoningPush,
206
- isToolRequestPush
244
+ isToolRequestPush,
245
+ toUint8
207
246
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-shared",
3
- "version": "0.1.0-next.3",
3
+ "version": "0.1.0-next.4",
4
4
  "description": "ReiStandard Active Messaging shared types and push builders — the lowest layer (no deps on other amsg packages)",
5
5
  "repository": {
6
6
  "type": "git",