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

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
@@ -62,6 +62,7 @@ function buildContentPush(args) {
62
62
  if (typeof args.message !== "string") {
63
63
  throw new Error("[amsg-shared] ContentPush: 'message' must be a string");
64
64
  }
65
+ validateNotificationArg("ContentPush", args.notification);
65
66
  const push = {
66
67
  messageKind: "content",
67
68
  messageType: args.messageType,
@@ -79,6 +80,7 @@ function buildContentPush(args) {
79
80
  if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
80
81
  if (args.taskId !== void 0) push.taskId = args.taskId;
81
82
  if (args.metadata !== void 0) push.metadata = args.metadata;
83
+ if (args.notification !== void 0) push.notification = args.notification;
82
84
  return push;
83
85
  }
84
86
  function buildReasoningPush(args) {
@@ -117,6 +119,7 @@ function buildToolRequestPush(args) {
117
119
  if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
118
120
  throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
119
121
  }
122
+ validateNotificationArg("ToolRequestPush", args.notification);
120
123
  const push = {
121
124
  messageKind: "tool_request",
122
125
  messageType: args.messageType,
@@ -131,8 +134,29 @@ function buildToolRequestPush(args) {
131
134
  if (args.message !== void 0) push.message = args.message;
132
135
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
133
136
  if (args.metadata !== void 0) push.metadata = args.metadata;
137
+ if (args.notification !== void 0) push.notification = args.notification;
134
138
  return push;
135
139
  }
140
+ function validateNotificationArg(kind, value) {
141
+ if (value === void 0) return;
142
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
143
+ throw new Error(`[amsg-shared] ${kind}: 'notification' must be a plain object`);
144
+ }
145
+ const n = (
146
+ /** @type {Record<string, unknown>} */
147
+ value
148
+ );
149
+ for (const f of ["title", "body", "icon", "badge", "tag"]) {
150
+ if (n[f] !== void 0 && typeof n[f] !== "string") {
151
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
152
+ }
153
+ }
154
+ for (const f of ["renotify", "requireInteraction"]) {
155
+ if (n[f] !== void 0 && typeof n[f] !== "boolean") {
156
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
157
+ }
158
+ }
159
+ }
136
160
  function buildErrorPush(args) {
137
161
  requireField("ErrorPush", "messageType", args.messageType);
138
162
  requireField("ErrorPush", "source", args.source);
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
@@ -320,6 +337,58 @@ export type AmsgPushCommon = {
320
337
  */
321
338
  metadata?: any;
322
339
  };
340
+ /**
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.
347
+ *
348
+ * 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
351
+ * the top-level `title` / `avatarUrl` / `messageId` and finally
352
+ * to the SW's `defaultIcon` / `defaultBadge` options. Everything
353
+ * 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.)
361
+ */
362
+ export type NotificationDirective = {
363
+ /**
364
+ * - Notification title override.
365
+ */
366
+ title?: string;
367
+ /**
368
+ * - Notification body override.
369
+ */
370
+ body?: string;
371
+ /**
372
+ * - Icon URL override (falls back to top-level `avatarUrl` then SW `defaultIcon`).
373
+ */
374
+ icon?: string;
375
+ /**
376
+ * - Badge URL override (falls back to SW `defaultBadge`).
377
+ */
378
+ badge?: string;
379
+ /**
380
+ * - Notification grouping tag; matching tag replaces the prior notification.
381
+ */
382
+ tag?: string;
383
+ /**
384
+ * - When tag matches, still vibrate/sound. Default false at SW.
385
+ */
386
+ renotify?: boolean;
387
+ /**
388
+ * - Notification stays until user dismisses. Default false at SW.
389
+ */
390
+ requireInteraction?: boolean;
391
+ };
323
392
  /**
324
393
  * Final user-facing content. Sentence-split bursts of N use
325
394
  * `messageIndex` (1-based) + `totalMessages` so the client can
@@ -334,6 +403,7 @@ export type ContentPush = AmsgPushCommon & {
334
403
  messageIndex?: number;
335
404
  totalMessages?: number;
336
405
  taskId?: string | null;
406
+ notification?: NotificationDirective;
337
407
  };
338
408
  /**
339
409
  * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
@@ -385,6 +455,7 @@ export type ToolRequestPush = AmsgPushCommon & {
385
455
  title?: string;
386
456
  contactName?: string;
387
457
  message?: string;
458
+ notification?: NotificationDirective;
388
459
  };
389
460
  /**
390
461
  * Producer-level error. Replaces the legacy
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
@@ -320,6 +337,58 @@ export type AmsgPushCommon = {
320
337
  */
321
338
  metadata?: any;
322
339
  };
340
+ /**
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.
347
+ *
348
+ * 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
351
+ * the top-level `title` / `avatarUrl` / `messageId` and finally
352
+ * to the SW's `defaultIcon` / `defaultBadge` options. Everything
353
+ * 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.)
361
+ */
362
+ export type NotificationDirective = {
363
+ /**
364
+ * - Notification title override.
365
+ */
366
+ title?: string;
367
+ /**
368
+ * - Notification body override.
369
+ */
370
+ body?: string;
371
+ /**
372
+ * - Icon URL override (falls back to top-level `avatarUrl` then SW `defaultIcon`).
373
+ */
374
+ icon?: string;
375
+ /**
376
+ * - Badge URL override (falls back to SW `defaultBadge`).
377
+ */
378
+ badge?: string;
379
+ /**
380
+ * - Notification grouping tag; matching tag replaces the prior notification.
381
+ */
382
+ tag?: string;
383
+ /**
384
+ * - When tag matches, still vibrate/sound. Default false at SW.
385
+ */
386
+ renotify?: boolean;
387
+ /**
388
+ * - Notification stays until user dismisses. Default false at SW.
389
+ */
390
+ requireInteraction?: boolean;
391
+ };
323
392
  /**
324
393
  * Final user-facing content. Sentence-split bursts of N use
325
394
  * `messageIndex` (1-based) + `totalMessages` so the client can
@@ -334,6 +403,7 @@ export type ContentPush = AmsgPushCommon & {
334
403
  messageIndex?: number;
335
404
  totalMessages?: number;
336
405
  taskId?: string | null;
406
+ notification?: NotificationDirective;
337
407
  };
338
408
  /**
339
409
  * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
@@ -385,6 +455,7 @@ export type ToolRequestPush = AmsgPushCommon & {
385
455
  title?: string;
386
456
  contactName?: string;
387
457
  message?: string;
458
+ notification?: NotificationDirective;
388
459
  };
389
460
  /**
390
461
  * Producer-level error. Replaces the legacy
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) {
@@ -83,6 +85,7 @@ function buildToolRequestPush(args) {
83
85
  if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
84
86
  throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
85
87
  }
88
+ validateNotificationArg("ToolRequestPush", args.notification);
86
89
  const push = {
87
90
  messageKind: "tool_request",
88
91
  messageType: args.messageType,
@@ -97,8 +100,29 @@ function buildToolRequestPush(args) {
97
100
  if (args.message !== void 0) push.message = args.message;
98
101
  if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
99
102
  if (args.metadata !== void 0) push.metadata = args.metadata;
103
+ if (args.notification !== void 0) push.notification = args.notification;
100
104
  return push;
101
105
  }
106
+ function validateNotificationArg(kind, value) {
107
+ if (value === void 0) return;
108
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
109
+ throw new Error(`[amsg-shared] ${kind}: 'notification' must be a plain object`);
110
+ }
111
+ const n = (
112
+ /** @type {Record<string, unknown>} */
113
+ value
114
+ );
115
+ for (const f of ["title", "body", "icon", "badge", "tag"]) {
116
+ if (n[f] !== void 0 && typeof n[f] !== "string") {
117
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a string when present`);
118
+ }
119
+ }
120
+ for (const f of ["renotify", "requireInteraction"]) {
121
+ if (n[f] !== void 0 && typeof n[f] !== "boolean") {
122
+ throw new Error(`[amsg-shared] ${kind}: 'notification.${f}' must be a boolean when present`);
123
+ }
124
+ }
125
+ }
102
126
  function buildErrorPush(args) {
103
127
  requireField("ErrorPush", "messageType", args.messageType);
104
128
  requireField("ErrorPush", "source", args.source);
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.3",
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",