@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 +64 -1
- package/dist/index.d.cts +92 -0
- package/dist/index.d.ts +92 -0
- package/dist/index.mjs +64 -1
- package/package.json +1 -1
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