@rei-standard/amsg-shared 0.1.0-next.2 → 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({
@@ -62,6 +65,7 @@ function buildContentPush(args) {
62
65
  if (typeof args.message !== "string") {
63
66
  throw new Error("[amsg-shared] ContentPush: 'message' must be a string");
64
67
  }
68
+ validateNotificationArg("ContentPush", args.notification);
65
69
  const push = {
66
70
  messageKind: "content",
67
71
  messageType: args.messageType,
@@ -79,6 +83,7 @@ function buildContentPush(args) {
79
83
  if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
80
84
  if (args.taskId !== void 0) push.taskId = args.taskId;
81
85
  if (args.metadata !== void 0) push.metadata = args.metadata;
86
+ if (args.notification !== void 0) push.notification = args.notification;
82
87
  return push;
83
88
  }
84
89
  function buildReasoningPush(args) {
@@ -89,6 +94,7 @@ function buildReasoningPush(args) {
89
94
  if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
90
95
  throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
91
96
  }
97
+ validateNotificationArg("ReasoningPush", args.notification);
92
98
  const push = {
93
99
  messageKind: "reasoning",
94
100
  messageType: args.messageType,
@@ -107,6 +113,7 @@ function buildReasoningPush(args) {
107
113
  if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
108
114
  if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
109
115
  if (args.metadata !== void 0) push.metadata = args.metadata;
116
+ if (args.notification !== void 0) push.notification = args.notification;
110
117
  return push;
111
118
  }
112
119
  function buildToolRequestPush(args) {
@@ -117,6 +124,7 @@ function buildToolRequestPush(args) {
117
124
  if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
118
125
  throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
119
126
  }
127
+ validateNotificationArg("ToolRequestPush", args.notification);
120
128
  const push = {
121
129
  messageKind: "tool_request",
122
130
  messageType: args.messageType,
@@ -131,8 +139,35 @@ function buildToolRequestPush(args) {
131
139
  if (args.message !== void 0) push.message = args.message;
132
140
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
133
141
  if (args.metadata !== void 0) push.metadata = args.metadata;
142
+ if (args.notification !== void 0) push.notification = args.notification;
134
143
  return push;
135
144
  }
145
+ function validateNotificationArg(kind, value) {
146
+ if (value === void 0) return;
147
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
148
+ throw new Error(`[amsg-shared] ${kind}: 'notification' must be a plain object`);
149
+ }
150
+ const n = (
151
+ /** @type {Record<string, unknown>} */
152
+ value
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
+ }
157
+ for (const f of ["title", "body", "icon", "badge", "tag"]) {
158
+ if (n[f] !== void 0 && typeof n[f] !== "string") {
159
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
160
+ }
161
+ }
162
+ for (const f of ["renotify", "requireInteraction"]) {
163
+ if (n[f] !== void 0 && typeof n[f] !== "boolean") {
164
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
165
+ }
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
+ }
170
+ }
136
171
  function buildErrorPush(args) {
137
172
  requireField("ErrorPush", "messageType", args.messageType);
138
173
  requireField("ErrorPush", "source", args.source);
@@ -142,6 +177,7 @@ function buildErrorPush(args) {
142
177
  if (typeof args.message !== "string") {
143
178
  throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
144
179
  }
180
+ validateNotificationArg("ErrorPush", args.notification);
145
181
  const push = {
146
182
  messageKind: "error",
147
183
  messageType: args.messageType,
@@ -155,6 +191,7 @@ function buildErrorPush(args) {
155
191
  if (args.iteration !== void 0) push.iteration = args.iteration;
156
192
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
157
193
  if (args.metadata !== void 0) push.metadata = args.metadata;
194
+ if (args.notification !== void 0) push.notification = args.notification;
158
195
  return push;
159
196
  }
160
197
  function isContentPush(value) {
@@ -201,3 +238,29 @@ function chunkReasoningByUtf8Bytes(text, maxBytes) {
201
238
  }
202
239
  return chunks;
203
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
@@ -18,6 +18,12 @@
18
18
  * @param {number} [args.totalMessages]
19
19
  * @param {string | null} [args.taskId]
20
20
  * @param {Object} [args.metadata]
21
+ * @param {NotificationDirective} [args.notification]
22
+ * - SW-side `showNotification` overrides for content
23
+ * (and for ToolRequestPush prefix chunks that get
24
+ * demoted to `content` during sentence-split). All
25
+ * fields optional; see {@link NotificationDirective}
26
+ * for the SW fallback chain.
21
27
  * @returns {ContentPush}
22
28
  */
23
29
  export function buildContentPush(args: {
@@ -35,6 +41,7 @@ export function buildContentPush(args: {
35
41
  totalMessages?: number;
36
42
  taskId?: string | null;
37
43
  metadata?: any;
44
+ notification?: NotificationDirective;
38
45
  }): ContentPush;
39
46
  /**
40
47
  * Build a {@link ReasoningPush}. Producers emit this **before** any
@@ -104,6 +111,15 @@ export function buildReasoningPush(args: {
104
111
  * @param {string} [args.message]
105
112
  * @param {string} [args.messageSubtype]
106
113
  * @param {Object} [args.metadata]
114
+ * @param {NotificationDirective} [args.notification]
115
+ * - SW notification overrides. Used after the
116
+ * splitter demotes prefix chunks to `content`
117
+ * (where `messageKind: 'content'` triggers
118
+ * `showNotification`). On the un-demoted last
119
+ * chunk (`messageKind: 'tool_request'`) the
120
+ * SW dispatches silently and the field is
121
+ * ignored — typed here purely so the demoted
122
+ * chunks inherit it via the splitter's spread.
107
123
  * @returns {ToolRequestPush}
108
124
  */
109
125
  export function buildToolRequestPush(args: {
@@ -118,6 +134,7 @@ export function buildToolRequestPush(args: {
118
134
  message?: string;
119
135
  messageSubtype?: string;
120
136
  metadata?: any;
137
+ notification?: NotificationDirective;
121
138
  }): ToolRequestPush;
122
139
  /**
123
140
  * Build an {@link ErrorPush}. Replaces the legacy
@@ -136,6 +153,7 @@ export function buildToolRequestPush(args: {
136
153
  * @param {number} [args.iteration]
137
154
  * @param {string} [args.messageSubtype]
138
155
  * @param {Object} [args.metadata]
156
+ * @param {NotificationDirective} [args.notification]
139
157
  * @returns {ErrorPush}
140
158
  */
141
159
  export function buildErrorPush(args: {
@@ -149,6 +167,7 @@ export function buildErrorPush(args: {
149
167
  iteration?: number;
150
168
  messageSubtype?: string;
151
169
  metadata?: any;
170
+ notification?: NotificationDirective;
152
171
  }): ErrorPush;
153
172
  /**
154
173
  * Type guard: returns true if the argument is a {@link ContentPush}.
@@ -212,6 +231,22 @@ export function isErrorPush(value: unknown): value is ErrorPush;
212
231
  * @returns {string[]}
213
232
  */
214
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;
215
250
  /**
216
251
  * @rei-standard/amsg-shared
217
252
  *
@@ -319,6 +354,63 @@ export type AmsgPushCommon = {
319
354
  * - Caller passthrough. Packages MUST NOT write here.
320
355
  */
321
356
  metadata?: any;
357
+ /**
358
+ * - SW notification strategy.
359
+ */
360
+ notification?: NotificationDirective;
361
+ };
362
+ /**
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.
366
+ *
367
+ * Routing in SW (kept here so producers don't have to cross-check):
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
372
+ * the top-level `title` / `avatarUrl` / `messageId` and finally
373
+ * to the SW's `defaultIcon` / `defaultBadge` options. Everything
374
+ * else (`tag`, `renotify`, `requireInteraction`) has no top-level
375
+ * fallback — set them under `notification` or accept the SW default.
376
+ */
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;
382
+ /**
383
+ * - Notification title override.
384
+ */
385
+ title?: string;
386
+ /**
387
+ * - Notification body override.
388
+ */
389
+ body?: string;
390
+ /**
391
+ * - Icon URL override (falls back to top-level `avatarUrl` then SW `defaultIcon`).
392
+ */
393
+ icon?: string;
394
+ /**
395
+ * - Badge URL override (falls back to SW `defaultBadge`).
396
+ */
397
+ badge?: string;
398
+ /**
399
+ * - Notification grouping tag; matching tag replaces the prior notification.
400
+ */
401
+ tag?: string;
402
+ /**
403
+ * - When tag matches, still vibrate/sound. Default false at SW.
404
+ */
405
+ renotify?: boolean;
406
+ /**
407
+ * - Notification stays until user dismisses. Default false at SW.
408
+ */
409
+ requireInteraction?: boolean;
410
+ /**
411
+ * - Custom payload data to attach to the notification.
412
+ */
413
+ data?: Record<string, unknown>;
322
414
  };
323
415
  /**
324
416
  * Final user-facing content. Sentence-split bursts of N use
package/dist/index.d.ts CHANGED
@@ -18,6 +18,12 @@
18
18
  * @param {number} [args.totalMessages]
19
19
  * @param {string | null} [args.taskId]
20
20
  * @param {Object} [args.metadata]
21
+ * @param {NotificationDirective} [args.notification]
22
+ * - SW-side `showNotification` overrides for content
23
+ * (and for ToolRequestPush prefix chunks that get
24
+ * demoted to `content` during sentence-split). All
25
+ * fields optional; see {@link NotificationDirective}
26
+ * for the SW fallback chain.
21
27
  * @returns {ContentPush}
22
28
  */
23
29
  export function buildContentPush(args: {
@@ -35,6 +41,7 @@ export function buildContentPush(args: {
35
41
  totalMessages?: number;
36
42
  taskId?: string | null;
37
43
  metadata?: any;
44
+ notification?: NotificationDirective;
38
45
  }): ContentPush;
39
46
  /**
40
47
  * Build a {@link ReasoningPush}. Producers emit this **before** any
@@ -104,6 +111,15 @@ export function buildReasoningPush(args: {
104
111
  * @param {string} [args.message]
105
112
  * @param {string} [args.messageSubtype]
106
113
  * @param {Object} [args.metadata]
114
+ * @param {NotificationDirective} [args.notification]
115
+ * - SW notification overrides. Used after the
116
+ * splitter demotes prefix chunks to `content`
117
+ * (where `messageKind: 'content'` triggers
118
+ * `showNotification`). On the un-demoted last
119
+ * chunk (`messageKind: 'tool_request'`) the
120
+ * SW dispatches silently and the field is
121
+ * ignored — typed here purely so the demoted
122
+ * chunks inherit it via the splitter's spread.
107
123
  * @returns {ToolRequestPush}
108
124
  */
109
125
  export function buildToolRequestPush(args: {
@@ -118,6 +134,7 @@ export function buildToolRequestPush(args: {
118
134
  message?: string;
119
135
  messageSubtype?: string;
120
136
  metadata?: any;
137
+ notification?: NotificationDirective;
121
138
  }): ToolRequestPush;
122
139
  /**
123
140
  * Build an {@link ErrorPush}. Replaces the legacy
@@ -136,6 +153,7 @@ export function buildToolRequestPush(args: {
136
153
  * @param {number} [args.iteration]
137
154
  * @param {string} [args.messageSubtype]
138
155
  * @param {Object} [args.metadata]
156
+ * @param {NotificationDirective} [args.notification]
139
157
  * @returns {ErrorPush}
140
158
  */
141
159
  export function buildErrorPush(args: {
@@ -149,6 +167,7 @@ export function buildErrorPush(args: {
149
167
  iteration?: number;
150
168
  messageSubtype?: string;
151
169
  metadata?: any;
170
+ notification?: NotificationDirective;
152
171
  }): ErrorPush;
153
172
  /**
154
173
  * Type guard: returns true if the argument is a {@link ContentPush}.
@@ -212,6 +231,22 @@ export function isErrorPush(value: unknown): value is ErrorPush;
212
231
  * @returns {string[]}
213
232
  */
214
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;
215
250
  /**
216
251
  * @rei-standard/amsg-shared
217
252
  *
@@ -319,6 +354,63 @@ export type AmsgPushCommon = {
319
354
  * - Caller passthrough. Packages MUST NOT write here.
320
355
  */
321
356
  metadata?: any;
357
+ /**
358
+ * - SW notification strategy.
359
+ */
360
+ notification?: NotificationDirective;
361
+ };
362
+ /**
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.
366
+ *
367
+ * Routing in SW (kept here so producers don't have to cross-check):
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
372
+ * the top-level `title` / `avatarUrl` / `messageId` and finally
373
+ * to the SW's `defaultIcon` / `defaultBadge` options. Everything
374
+ * else (`tag`, `renotify`, `requireInteraction`) has no top-level
375
+ * fallback — set them under `notification` or accept the SW default.
376
+ */
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;
382
+ /**
383
+ * - Notification title override.
384
+ */
385
+ title?: string;
386
+ /**
387
+ * - Notification body override.
388
+ */
389
+ body?: string;
390
+ /**
391
+ * - Icon URL override (falls back to top-level `avatarUrl` then SW `defaultIcon`).
392
+ */
393
+ icon?: string;
394
+ /**
395
+ * - Badge URL override (falls back to SW `defaultBadge`).
396
+ */
397
+ badge?: string;
398
+ /**
399
+ * - Notification grouping tag; matching tag replaces the prior notification.
400
+ */
401
+ tag?: string;
402
+ /**
403
+ * - When tag matches, still vibrate/sound. Default false at SW.
404
+ */
405
+ renotify?: boolean;
406
+ /**
407
+ * - Notification stays until user dismisses. Default false at SW.
408
+ */
409
+ requireInteraction?: boolean;
410
+ /**
411
+ * - Custom payload data to attach to the notification.
412
+ */
413
+ data?: Record<string, unknown>;
322
414
  };
323
415
  /**
324
416
  * Final user-facing content. Sentence-split bursts of N use
package/dist/index.mjs CHANGED
@@ -28,6 +28,7 @@ function buildContentPush(args) {
28
28
  if (typeof args.message !== "string") {
29
29
  throw new Error("[amsg-shared] ContentPush: 'message' must be a string");
30
30
  }
31
+ validateNotificationArg("ContentPush", args.notification);
31
32
  const push = {
32
33
  messageKind: "content",
33
34
  messageType: args.messageType,
@@ -45,6 +46,7 @@ function buildContentPush(args) {
45
46
  if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
46
47
  if (args.taskId !== void 0) push.taskId = args.taskId;
47
48
  if (args.metadata !== void 0) push.metadata = args.metadata;
49
+ if (args.notification !== void 0) push.notification = args.notification;
48
50
  return push;
49
51
  }
50
52
  function buildReasoningPush(args) {
@@ -55,6 +57,7 @@ function buildReasoningPush(args) {
55
57
  if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
56
58
  throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
57
59
  }
60
+ validateNotificationArg("ReasoningPush", args.notification);
58
61
  const push = {
59
62
  messageKind: "reasoning",
60
63
  messageType: args.messageType,
@@ -73,6 +76,7 @@ function buildReasoningPush(args) {
73
76
  if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
74
77
  if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
75
78
  if (args.metadata !== void 0) push.metadata = args.metadata;
79
+ if (args.notification !== void 0) push.notification = args.notification;
76
80
  return push;
77
81
  }
78
82
  function buildToolRequestPush(args) {
@@ -83,6 +87,7 @@ function buildToolRequestPush(args) {
83
87
  if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
84
88
  throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
85
89
  }
90
+ validateNotificationArg("ToolRequestPush", args.notification);
86
91
  const push = {
87
92
  messageKind: "tool_request",
88
93
  messageType: args.messageType,
@@ -97,8 +102,35 @@ function buildToolRequestPush(args) {
97
102
  if (args.message !== void 0) push.message = args.message;
98
103
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
99
104
  if (args.metadata !== void 0) push.metadata = args.metadata;
105
+ if (args.notification !== void 0) push.notification = args.notification;
100
106
  return push;
101
107
  }
108
+ function validateNotificationArg(kind, value) {
109
+ if (value === void 0) return;
110
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
111
+ throw new Error(`[amsg-shared] ${kind}: 'notification' must be a plain object`);
112
+ }
113
+ const n = (
114
+ /** @type {Record<string, unknown>} */
115
+ value
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
+ }
120
+ for (const f of ["title", "body", "icon", "badge", "tag"]) {
121
+ if (n[f] !== void 0 && typeof n[f] !== "string") {
122
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
123
+ }
124
+ }
125
+ for (const f of ["renotify", "requireInteraction"]) {
126
+ if (n[f] !== void 0 && typeof n[f] !== "boolean") {
127
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
128
+ }
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
+ }
133
+ }
102
134
  function buildErrorPush(args) {
103
135
  requireField("ErrorPush", "messageType", args.messageType);
104
136
  requireField("ErrorPush", "source", args.source);
@@ -108,6 +140,7 @@ function buildErrorPush(args) {
108
140
  if (typeof args.message !== "string") {
109
141
  throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
110
142
  }
143
+ validateNotificationArg("ErrorPush", args.notification);
111
144
  const push = {
112
145
  messageKind: "error",
113
146
  messageType: args.messageType,
@@ -121,6 +154,7 @@ function buildErrorPush(args) {
121
154
  if (args.iteration !== void 0) push.iteration = args.iteration;
122
155
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
123
156
  if (args.metadata !== void 0) push.metadata = args.metadata;
157
+ if (args.notification !== void 0) push.notification = args.notification;
124
158
  return push;
125
159
  }
126
160
  function isContentPush(value) {
@@ -167,17 +201,46 @@ function chunkReasoningByUtf8Bytes(text, maxBytes) {
167
201
  }
168
202
  return chunks;
169
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
+ }
170
230
  export {
171
231
  MESSAGE_KIND,
172
232
  MESSAGE_TYPE,
173
233
  PUSH_SOURCE,
234
+ base64UrlToBytes,
174
235
  buildContentPush,
175
236
  buildErrorPush,
176
237
  buildReasoningPush,
177
238
  buildToolRequestPush,
178
239
  chunkReasoningByUtf8Bytes,
240
+ concatBytes,
179
241
  isContentPush,
180
242
  isErrorPush,
181
243
  isReasoningPush,
182
- isToolRequestPush
244
+ isToolRequestPush,
245
+ toUint8
183
246
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rei-standard/amsg-shared",
3
- "version": "0.1.0-next.2",
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",