@rei-standard/amsg-shared 0.1.0-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,253 @@
1
+ # @rei-standard/amsg-shared
2
+
3
+ Lowest layer of the ReiStandard Active Messaging ecosystem. Defines
4
+ the **three-axis push contract** that `amsg-instant`, `amsg-server`,
5
+ `amsg-sw`, and `amsg-client` all conform to.
6
+
7
+ Zero runtime deps. Does **not** depend on any other amsg package —
8
+ every other amsg sub-package depends on this one, never the reverse.
9
+
10
+ ---
11
+
12
+ ## Three axes
13
+
14
+ A single push is described by three orthogonal axes:
15
+
16
+ | Axis | Field | Values | Defined by |
17
+ |----------------|-------------------|-------------------------------------------------------|--------------------|
18
+ | Dispatch | `messageType` | `instant` / `fixed` / `prompted` / `auto` | Package (fixed) |
19
+ | Business | `messageSubtype` | Any string | Caller (free-form) |
20
+ | Content | `messageKind` | `content` / `reasoning` / `tool_request` / `error` | Package (fixed) |
21
+
22
+ `messageType` answers **how this push was produced** (one-shot
23
+ `instant` worker, scheduled `fixed` ping, AI-`prompted` reply, fully
24
+ `auto`-generated cadence). `messageKind` answers **what it carries**.
25
+ The two are intentionally orthogonal: any `messageType` can carry any
26
+ `messageKind`.
27
+
28
+ There is also `source: 'instant' | 'scheduled'` — the **routing
29
+ origin** (`'instant'` for `amsg-instant`, `'scheduled'` for any
30
+ `amsg-server` output). `messageType: 'instant'` always pairs with
31
+ `source: 'instant'`; the other three `messageType`s always pair with
32
+ `source: 'scheduled'`.
33
+
34
+ ---
35
+
36
+ ## Common fields (every push)
37
+
38
+ | Field | Type | Notes |
39
+ |------------------|-------------------|-----------------------------------------------------------------------------|
40
+ | `messageKind` | `MessageKind` | Discriminator. Literal type — TS narrows on it. |
41
+ | `messageType` | `MessageType` | Dispatch axis. |
42
+ | `source` | `'instant' \| 'scheduled'` | Routing origin. |
43
+ | `messageId` | `string` | Unique per push. Format owned by the producer. |
44
+ | `sessionId` | `string` | **Shared across all pushes from the same LLM round** (reasoning + content), and across all iterations of a single agentic-loop request. |
45
+ | `timestamp` | `string` (ISO 8601) | Producer-side wall clock. |
46
+ | `messageSubtype` | `string?` | Caller's business namespace. Defaults to `'chat'` at producers. |
47
+ | `metadata` | `object?` | **Caller passthrough.** Packages MUST NOT write here. |
48
+
49
+ ---
50
+
51
+ ## Per-kind fields
52
+
53
+ ### `ContentPush` — final user-facing content
54
+
55
+ | Field | Type | Notes |
56
+ |------------------|-------------|----------------------------------------------------------------|
57
+ | `messageKind` | `'content'` | Discriminator. |
58
+ | `message` | `string` | The sentence/segment to display. |
59
+ | `messageIndex` | `number?` | 1-based segment index within an N-split burst. Omit for singletons. |
60
+ | `totalMessages` | `number?` | Total segments in the burst. Omit for singletons. |
61
+ | `title` | `string?` | Notification title. |
62
+ | `contactName` | `string?` | Sender display name. |
63
+ | `avatarUrl` | `string \| null?` | Sender avatar URL (`https:` only — `data:` is rejected upstream). |
64
+ | `taskId` | `string \| null?` | Scheduled task ID (server only). |
65
+
66
+ ### `ReasoningPush` — LLM meta-thinking
67
+
68
+ | Field | Type | Notes |
69
+ |--------------------|----------------|-------------------------------------------------------------|
70
+ | `messageKind` | `'reasoning'` | Discriminator. |
71
+ | `reasoningContent` | `string` | Lifted from `choices[0].message.reasoning_content`. |
72
+ | `title` | `string?` | |
73
+ | `contactName` | `string?` | |
74
+ | `avatarUrl` | `string \| null?` | |
75
+
76
+ **No `messageIndex` / `totalMessages`.** Reasoning is one push per
77
+ LLM round, never a split-burst. Those fields are absent at the type
78
+ level on purpose — making them optional would leave callers
79
+ wondering when they're set.
80
+
81
+ Emitted **before** the matching `ContentPush` burst when the LLM
82
+ response carried a non-empty `reasoning_content`.
83
+
84
+ ### `ToolRequestPush` — tool invocation request
85
+
86
+ | Field | Type | Notes |
87
+ |---------------|------------------|-------------------------------------------------------------|
88
+ | `messageKind` | `'tool_request'` | Discriminator. |
89
+ | `toolCalls` | `Array<object>` | OpenAI `choices[0].message.tool_calls` shape, passthrough. |
90
+ | `title` | `string?` | |
91
+ | `contactName` | `string?` | |
92
+ | `message` | `string?` | Optional human-readable tag for the request. |
93
+
94
+ Emitted by an agentic-loop hook returning
95
+ `{ decision: 'tool-request', pushPayload }`. The client is expected
96
+ to execute the tool and resume via `/continue`.
97
+
98
+ ### `ErrorPush` — producer-level error
99
+
100
+ | Field | Type | Notes |
101
+ |---------------|-----------|------------------------------------------------------------------------|
102
+ | `messageKind` | `'error'` | Discriminator. |
103
+ | `code` | `string` | Stable producer-defined code, e.g. `HOOK_THREW`, `LOOP_EXCEEDED`. |
104
+ | `message` | `string` | Human-readable description. |
105
+ | `iteration` | `number?` | Agentic-loop iteration when relevant. |
106
+
107
+ Replaces the legacy 0.7.0 `{ type: 'error', code: '...' }` envelope.
108
+ The legacy `type` field is **gone** — do not look for it on
109
+ `ErrorPush`.
110
+
111
+ ---
112
+
113
+ ## Usage
114
+
115
+ ### TypeScript / typed JavaScript
116
+
117
+ ```ts
118
+ import {
119
+ type AmsgPush,
120
+ type ContentPush,
121
+ type ReasoningPush,
122
+ isContentPush,
123
+ } from '@rei-standard/amsg-shared';
124
+
125
+ function dispatch(push: AmsgPush) {
126
+ switch (push.messageKind) {
127
+ case 'content':
128
+ // push narrowed to ContentPush — push.message is `string`
129
+ console.log(push.message);
130
+ break;
131
+ case 'reasoning':
132
+ // push narrowed to ReasoningPush — push.reasoningContent is `string`
133
+ console.log(push.reasoningContent);
134
+ break;
135
+ case 'tool_request':
136
+ // push.toolCalls is `Array<object>`
137
+ break;
138
+ case 'error':
139
+ console.error(push.code, push.message);
140
+ break;
141
+ }
142
+ }
143
+ ```
144
+
145
+ ### Builders
146
+
147
+ ```js
148
+ import {
149
+ buildContentPush,
150
+ buildReasoningPush,
151
+ buildToolRequestPush,
152
+ buildErrorPush,
153
+ } from '@rei-standard/amsg-shared';
154
+
155
+ // One sentence in an N-split burst
156
+ const content = buildContentPush({
157
+ messageType: 'instant',
158
+ source: 'instant',
159
+ messageId: `msg_${crypto.randomUUID()}_0`,
160
+ sessionId: 'sess_abc',
161
+ message: 'Hello!',
162
+ contactName: 'Rei',
163
+ messageIndex: 1,
164
+ totalMessages: 2,
165
+ });
166
+
167
+ // Reasoning emitted before the content burst
168
+ const reasoning = buildReasoningPush({
169
+ messageType: 'instant',
170
+ source: 'instant',
171
+ messageId: `msg_${crypto.randomUUID()}_reasoning`,
172
+ sessionId: 'sess_abc', // SAME sessionId as the content above
173
+ reasoningContent: 'User greeted me; I should reply warmly.',
174
+ });
175
+
176
+ // Agentic-loop tool request
177
+ const toolReq = buildToolRequestPush({
178
+ messageType: 'instant',
179
+ source: 'instant',
180
+ messageId: `msg_${crypto.randomUUID()}_tool`,
181
+ sessionId: 'sess_abc',
182
+ toolCalls: [{ id: 'call_0', type: 'function', function: { name: 'get_weather', arguments: '{}' } }],
183
+ });
184
+
185
+ // Producer-level error
186
+ const error = buildErrorPush({
187
+ messageType: 'instant',
188
+ source: 'instant',
189
+ messageId: `msg_${crypto.randomUUID()}_err`,
190
+ sessionId: 'sess_abc',
191
+ code: 'HOOK_THREW',
192
+ message: 'onLLMOutput threw: ...',
193
+ iteration: 2,
194
+ });
195
+ ```
196
+
197
+ ### Type guards
198
+
199
+ ```js
200
+ import { isContentPush, isReasoningPush, isErrorPush } from '@rei-standard/amsg-shared';
201
+
202
+ if (isContentPush(push)) {
203
+ // push.message is `string`
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ## Constants
210
+
211
+ ```js
212
+ import { MESSAGE_KIND, MESSAGE_TYPE, PUSH_SOURCE } from '@rei-standard/amsg-shared';
213
+
214
+ MESSAGE_KIND.CONTENT; // 'content'
215
+ MESSAGE_KIND.REASONING; // 'reasoning'
216
+ MESSAGE_KIND.TOOL_REQUEST; // 'tool_request'
217
+ MESSAGE_KIND.ERROR; // 'error'
218
+
219
+ MESSAGE_TYPE.INSTANT; // 'instant'
220
+ MESSAGE_TYPE.FIXED; // 'fixed'
221
+ MESSAGE_TYPE.PROMPTED; // 'prompted'
222
+ MESSAGE_TYPE.AUTO; // 'auto'
223
+
224
+ PUSH_SOURCE.INSTANT; // 'instant'
225
+ PUSH_SOURCE.SCHEDULED; // 'scheduled'
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Invariants
231
+
232
+ 1. **`messageKind` is a literal-type discriminator.** Producers must
233
+ set it via a builder (or to one of the literal values directly).
234
+ Never `string`-typed.
235
+ 2. **`sessionId` is stable across a single LLM round.** A
236
+ `ReasoningPush` and the `ContentPush`(es) it precedes share the
237
+ same `sessionId`. Agentic-loop multi-iteration runs reuse the
238
+ same `sessionId` across iterations.
239
+ 3. **`ReasoningPush` carries no `messageIndex` / `totalMessages`.**
240
+ Those fields belong to the content N-split burst.
241
+ 4. **`metadata` is caller-owned.** Packages must add protocol-level
242
+ data as top-level fields, never inside `metadata`.
243
+ 5. **`source` is the routing origin, not the dispatch type.**
244
+ `'instant'` ⇄ `amsg-instant`; `'scheduled'` ⇄ `amsg-server`.
245
+
246
+ See [§6 of `standards/active-messaging-api.md`](../../../standards/active-messaging-api.md)
247
+ for the wire-level contract.
248
+
249
+ ---
250
+
251
+ ## License
252
+
253
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,170 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __export = (target, all) => {
6
+ for (var name in all)
7
+ __defProp(target, name, { get: all[name], enumerable: true });
8
+ };
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
18
+
19
+ // src/index.js
20
+ var src_exports = {};
21
+ __export(src_exports, {
22
+ MESSAGE_KIND: () => MESSAGE_KIND,
23
+ MESSAGE_TYPE: () => MESSAGE_TYPE,
24
+ PUSH_SOURCE: () => PUSH_SOURCE,
25
+ buildContentPush: () => buildContentPush,
26
+ buildErrorPush: () => buildErrorPush,
27
+ buildReasoningPush: () => buildReasoningPush,
28
+ buildToolRequestPush: () => buildToolRequestPush,
29
+ isContentPush: () => isContentPush,
30
+ isErrorPush: () => isErrorPush,
31
+ isReasoningPush: () => isReasoningPush,
32
+ isToolRequestPush: () => isToolRequestPush
33
+ });
34
+ module.exports = __toCommonJS(src_exports);
35
+ var MESSAGE_KIND = Object.freeze({
36
+ CONTENT: "content",
37
+ REASONING: "reasoning",
38
+ TOOL_REQUEST: "tool_request",
39
+ ERROR: "error"
40
+ });
41
+ var MESSAGE_TYPE = Object.freeze({
42
+ INSTANT: "instant",
43
+ FIXED: "fixed",
44
+ PROMPTED: "prompted",
45
+ AUTO: "auto"
46
+ });
47
+ var PUSH_SOURCE = Object.freeze({
48
+ INSTANT: "instant",
49
+ SCHEDULED: "scheduled"
50
+ });
51
+ function requireField(kind, field, value) {
52
+ if (value === void 0 || value === null || value === "") {
53
+ throw new Error(`[amsg-shared] ${kind}: '${field}' is required`);
54
+ }
55
+ }
56
+ function buildContentPush(args) {
57
+ requireField("ContentPush", "messageType", args.messageType);
58
+ requireField("ContentPush", "source", args.source);
59
+ requireField("ContentPush", "messageId", args.messageId);
60
+ requireField("ContentPush", "sessionId", args.sessionId);
61
+ if (typeof args.message !== "string") {
62
+ throw new Error("[amsg-shared] ContentPush: 'message' must be a string");
63
+ }
64
+ const push = {
65
+ messageKind: "content",
66
+ messageType: args.messageType,
67
+ source: args.source,
68
+ messageId: args.messageId,
69
+ sessionId: args.sessionId,
70
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
71
+ message: args.message
72
+ };
73
+ if (args.title !== void 0) push.title = args.title;
74
+ if (args.contactName !== void 0) push.contactName = args.contactName;
75
+ if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
76
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
77
+ if (args.messageIndex !== void 0) push.messageIndex = args.messageIndex;
78
+ if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
79
+ if (args.taskId !== void 0) push.taskId = args.taskId;
80
+ if (args.metadata !== void 0) push.metadata = args.metadata;
81
+ return push;
82
+ }
83
+ function buildReasoningPush(args) {
84
+ requireField("ReasoningPush", "messageType", args.messageType);
85
+ requireField("ReasoningPush", "source", args.source);
86
+ requireField("ReasoningPush", "messageId", args.messageId);
87
+ requireField("ReasoningPush", "sessionId", args.sessionId);
88
+ if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
89
+ throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
90
+ }
91
+ const push = {
92
+ messageKind: "reasoning",
93
+ messageType: args.messageType,
94
+ source: args.source,
95
+ messageId: args.messageId,
96
+ sessionId: args.sessionId,
97
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
98
+ reasoningContent: args.reasoningContent
99
+ };
100
+ if (args.title !== void 0) push.title = args.title;
101
+ if (args.contactName !== void 0) push.contactName = args.contactName;
102
+ if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
103
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
104
+ if (args.metadata !== void 0) push.metadata = args.metadata;
105
+ return push;
106
+ }
107
+ function buildToolRequestPush(args) {
108
+ requireField("ToolRequestPush", "messageType", args.messageType);
109
+ requireField("ToolRequestPush", "source", args.source);
110
+ requireField("ToolRequestPush", "messageId", args.messageId);
111
+ requireField("ToolRequestPush", "sessionId", args.sessionId);
112
+ if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
113
+ throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
114
+ }
115
+ const push = {
116
+ messageKind: "tool_request",
117
+ messageType: args.messageType,
118
+ source: args.source,
119
+ messageId: args.messageId,
120
+ sessionId: args.sessionId,
121
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
122
+ toolCalls: args.toolCalls
123
+ };
124
+ if (args.title !== void 0) push.title = args.title;
125
+ if (args.contactName !== void 0) push.contactName = args.contactName;
126
+ if (args.message !== void 0) push.message = args.message;
127
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
128
+ if (args.metadata !== void 0) push.metadata = args.metadata;
129
+ return push;
130
+ }
131
+ function buildErrorPush(args) {
132
+ requireField("ErrorPush", "messageType", args.messageType);
133
+ requireField("ErrorPush", "source", args.source);
134
+ requireField("ErrorPush", "messageId", args.messageId);
135
+ requireField("ErrorPush", "sessionId", args.sessionId);
136
+ requireField("ErrorPush", "code", args.code);
137
+ if (typeof args.message !== "string") {
138
+ throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
139
+ }
140
+ const push = {
141
+ messageKind: "error",
142
+ messageType: args.messageType,
143
+ source: args.source,
144
+ messageId: args.messageId,
145
+ sessionId: args.sessionId,
146
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
147
+ code: args.code,
148
+ message: args.message
149
+ };
150
+ if (args.iteration !== void 0) push.iteration = args.iteration;
151
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
152
+ if (args.metadata !== void 0) push.metadata = args.metadata;
153
+ return push;
154
+ }
155
+ function isContentPush(value) {
156
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
157
+ value.messageKind === "content";
158
+ }
159
+ function isReasoningPush(value) {
160
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
161
+ value.messageKind === "reasoning";
162
+ }
163
+ function isToolRequestPush(value) {
164
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
165
+ value.messageKind === "tool_request";
166
+ }
167
+ function isErrorPush(value) {
168
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
169
+ value.messageKind === "error";
170
+ }
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Build a {@link ContentPush}. Use this for legacy sentence-split
3
+ * bursts (set `messageIndex` 1-based + `totalMessages`) or for a
4
+ * single content push (omit both).
5
+ *
6
+ * @param {Object} args
7
+ * @param {MessageType} args.messageType
8
+ * @param {PushSource} args.source
9
+ * @param {string} args.messageId
10
+ * @param {string} args.sessionId
11
+ * @param {string} args.message
12
+ * @param {string} [args.timestamp] - Defaults to `new Date().toISOString()`.
13
+ * @param {string} [args.title]
14
+ * @param {string} [args.contactName]
15
+ * @param {string | null} [args.avatarUrl]
16
+ * @param {string} [args.messageSubtype]
17
+ * @param {number} [args.messageIndex]
18
+ * @param {number} [args.totalMessages]
19
+ * @param {string | null} [args.taskId]
20
+ * @param {Object} [args.metadata]
21
+ * @returns {ContentPush}
22
+ */
23
+ export function buildContentPush(args: {
24
+ messageType: MessageType;
25
+ source: PushSource;
26
+ messageId: string;
27
+ sessionId: string;
28
+ message: string;
29
+ timestamp?: string;
30
+ title?: string;
31
+ contactName?: string;
32
+ avatarUrl?: string | null;
33
+ messageSubtype?: string;
34
+ messageIndex?: number;
35
+ totalMessages?: number;
36
+ taskId?: string | null;
37
+ metadata?: any;
38
+ }): ContentPush;
39
+ /**
40
+ * Build a {@link ReasoningPush}. Producers emit this **before** any
41
+ * matching `ContentPush` burst when the LLM response carried a non-
42
+ * empty `reasoning_content`.
43
+ *
44
+ * Does NOT take `messageIndex` / `totalMessages` — reasoning is one
45
+ * push per LLM round.
46
+ *
47
+ * @param {Object} args
48
+ * @param {MessageType} args.messageType
49
+ * @param {PushSource} args.source
50
+ * @param {string} args.messageId
51
+ * @param {string} args.sessionId
52
+ * @param {string} args.reasoningContent
53
+ * @param {string} [args.timestamp]
54
+ * @param {string} [args.title]
55
+ * @param {string} [args.contactName]
56
+ * @param {string | null} [args.avatarUrl]
57
+ * @param {string} [args.messageSubtype]
58
+ * @param {Object} [args.metadata]
59
+ * @returns {ReasoningPush}
60
+ */
61
+ export function buildReasoningPush(args: {
62
+ messageType: MessageType;
63
+ source: PushSource;
64
+ messageId: string;
65
+ sessionId: string;
66
+ reasoningContent: string;
67
+ timestamp?: string;
68
+ title?: string;
69
+ contactName?: string;
70
+ avatarUrl?: string | null;
71
+ messageSubtype?: string;
72
+ metadata?: any;
73
+ }): ReasoningPush;
74
+ /**
75
+ * Build a {@link ToolRequestPush}. Caller is expected to executed
76
+ * tools client-side and resume via `/continue` (see `amsg-instant`
77
+ * README §Agentic Loop).
78
+ *
79
+ * @param {Object} args
80
+ * @param {MessageType} args.messageType
81
+ * @param {PushSource} args.source
82
+ * @param {string} args.messageId
83
+ * @param {string} args.sessionId
84
+ * @param {Array<Object>} args.toolCalls
85
+ * @param {string} [args.timestamp]
86
+ * @param {string} [args.title]
87
+ * @param {string} [args.contactName]
88
+ * @param {string} [args.message]
89
+ * @param {string} [args.messageSubtype]
90
+ * @param {Object} [args.metadata]
91
+ * @returns {ToolRequestPush}
92
+ */
93
+ export function buildToolRequestPush(args: {
94
+ messageType: MessageType;
95
+ source: PushSource;
96
+ messageId: string;
97
+ sessionId: string;
98
+ toolCalls: Array<any>;
99
+ timestamp?: string;
100
+ title?: string;
101
+ contactName?: string;
102
+ message?: string;
103
+ messageSubtype?: string;
104
+ metadata?: any;
105
+ }): ToolRequestPush;
106
+ /**
107
+ * Build an {@link ErrorPush}. Replaces the legacy
108
+ * `{ type: 'error', code: '...' }` envelope. The new shape carries
109
+ * the full common-fields set so the SW can route it through the
110
+ * same `messageKind` switch as the other three kinds.
111
+ *
112
+ * @param {Object} args
113
+ * @param {MessageType} args.messageType
114
+ * @param {PushSource} args.source
115
+ * @param {string} args.messageId
116
+ * @param {string} args.sessionId
117
+ * @param {string} args.code
118
+ * @param {string} args.message
119
+ * @param {string} [args.timestamp]
120
+ * @param {number} [args.iteration]
121
+ * @param {string} [args.messageSubtype]
122
+ * @param {Object} [args.metadata]
123
+ * @returns {ErrorPush}
124
+ */
125
+ export function buildErrorPush(args: {
126
+ messageType: MessageType;
127
+ source: PushSource;
128
+ messageId: string;
129
+ sessionId: string;
130
+ code: string;
131
+ message: string;
132
+ timestamp?: string;
133
+ iteration?: number;
134
+ messageSubtype?: string;
135
+ metadata?: any;
136
+ }): ErrorPush;
137
+ /**
138
+ * Type guard: returns true if the argument is a {@link ContentPush}.
139
+ *
140
+ * @param {unknown} value
141
+ * @returns {value is ContentPush}
142
+ */
143
+ export function isContentPush(value: unknown): value is ContentPush;
144
+ /**
145
+ * Type guard: returns true if the argument is a {@link ReasoningPush}.
146
+ *
147
+ * @param {unknown} value
148
+ * @returns {value is ReasoningPush}
149
+ */
150
+ export function isReasoningPush(value: unknown): value is ReasoningPush;
151
+ /**
152
+ * Type guard: returns true if the argument is a {@link ToolRequestPush}.
153
+ *
154
+ * @param {unknown} value
155
+ * @returns {value is ToolRequestPush}
156
+ */
157
+ export function isToolRequestPush(value: unknown): value is ToolRequestPush;
158
+ /**
159
+ * Type guard: returns true if the argument is an {@link ErrorPush}.
160
+ *
161
+ * @param {unknown} value
162
+ * @returns {value is ErrorPush}
163
+ */
164
+ export function isErrorPush(value: unknown): value is ErrorPush;
165
+ /**
166
+ * @rei-standard/amsg-shared
167
+ *
168
+ * Lowest layer of the ReiStandard Active Messaging ecosystem.
169
+ * Defines the three-axis push contract that `amsg-instant`,
170
+ * `amsg-server`, `amsg-sw`, and `amsg-client` all conform to.
171
+ *
172
+ * Three orthogonal axes:
173
+ * 1. messageType — how the push was produced (instant / fixed / prompted / auto)
174
+ * 2. messageSubtype — caller's business classification (free-form string)
175
+ * 3. messageKind — what the push carries (content / reasoning / tool_request / error)
176
+ *
177
+ * Zero runtime dependencies. The package is ESM/CJS dual-published and
178
+ * intentionally has no `dependencies:` entry — every other amsg sub-
179
+ * package depends on it, never the reverse.
180
+ *
181
+ * Types are expressed via JSDoc `@typedef` unions with literal-type
182
+ * discriminators so TS consumers can narrow on `messageKind`:
183
+ *
184
+ * if (push.messageKind === 'reasoning') {
185
+ * // TS knows: push is ReasoningPush, push.reasoningContent is string
186
+ * }
187
+ */
188
+ /**
189
+ * What the push carries. Fixed enum — packages must not add values.
190
+ *
191
+ * @typedef {'content' | 'reasoning' | 'tool_request' | 'error'} MessageKind
192
+ */
193
+ /**
194
+ * How the push was produced. Fixed enum — packages must not add values.
195
+ *
196
+ * @typedef {'instant' | 'fixed' | 'prompted' | 'auto'} MessageType
197
+ */
198
+ /**
199
+ * Which sub-package routed the push. Fixed enum — `'instant'` for
200
+ * `amsg-instant` (stateless one-shot), `'scheduled'` for any
201
+ * `amsg-server` output regardless of `messageType`. Packages must not
202
+ * add values.
203
+ *
204
+ * @typedef {'instant' | 'scheduled'} PushSource
205
+ */
206
+ /**
207
+ * Runtime constant mirroring the {@link MessageKind} type. Useful for
208
+ * switch statements that need to enumerate every kind:
209
+ *
210
+ * for (const kind of Object.values(MESSAGE_KIND)) { ... }
211
+ */
212
+ export const MESSAGE_KIND: Readonly<{
213
+ CONTENT: "content";
214
+ REASONING: "reasoning";
215
+ TOOL_REQUEST: "tool_request";
216
+ ERROR: "error";
217
+ }>;
218
+ /**
219
+ * Runtime constant mirroring the {@link MessageType} type.
220
+ */
221
+ export const MESSAGE_TYPE: Readonly<{
222
+ INSTANT: "instant";
223
+ FIXED: "fixed";
224
+ PROMPTED: "prompted";
225
+ AUTO: "auto";
226
+ }>;
227
+ /**
228
+ * Runtime constant mirroring the {@link PushSource} type.
229
+ */
230
+ export const PUSH_SOURCE: Readonly<{
231
+ INSTANT: "instant";
232
+ SCHEDULED: "scheduled";
233
+ }>;
234
+ /**
235
+ * Fields present on every push, regardless of kind. Discriminator
236
+ * fields (`messageKind`) and kind-specific fields live on the kind
237
+ * interfaces below.
238
+ *
239
+ * `metadata` is a passthrough namespace owned by the caller. Packages
240
+ * are forbidden from writing their own fields into `metadata` — any
241
+ * protocol-level data goes on top-level fields.
242
+ */
243
+ export type AmsgPushCommon = {
244
+ /**
245
+ * - How the push was produced.
246
+ */
247
+ messageType: MessageType;
248
+ /**
249
+ * - Which sub-package routed it.
250
+ */
251
+ source: PushSource;
252
+ /**
253
+ * - Unique per push. Format owned by the producer.
254
+ */
255
+ messageId: string;
256
+ /**
257
+ * - Shared across all pushes from one LLM round (reasoning + content) and across iterations of a single agentic-loop request.
258
+ */
259
+ sessionId: string;
260
+ /**
261
+ * - ISO 8601 timestamp at producer.
262
+ */
263
+ timestamp: string;
264
+ /**
265
+ * - Caller-defined business namespace. Defaults to 'chat' at producers.
266
+ */
267
+ messageSubtype?: string;
268
+ /**
269
+ * - Caller passthrough. Packages MUST NOT write here.
270
+ */
271
+ metadata?: any;
272
+ };
273
+ /**
274
+ * Final user-facing content. Sentence-split bursts of N use
275
+ * `messageIndex` (1-based) + `totalMessages` so the client can
276
+ * reassemble or animate.
277
+ */
278
+ export type ContentPush = AmsgPushCommon & {
279
+ messageKind: "content";
280
+ message: string;
281
+ title?: string;
282
+ contactName?: string;
283
+ avatarUrl?: string | null;
284
+ messageIndex?: number;
285
+ totalMessages?: number;
286
+ taskId?: string | null;
287
+ };
288
+ /**
289
+ * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
290
+ * out of the upstream response into its own push. Emitted **before**
291
+ * the matching {@link ContentPush} burst when present and non-empty.
292
+ *
293
+ * Intentionally does NOT carry `messageIndex` / `totalMessages` —
294
+ * reasoning is a single push per LLM round, never a split-burst.
295
+ * That's why those fields are absent at the type level rather than
296
+ * `optional` (which would leave callers wondering when they're set).
297
+ */
298
+ export type ReasoningPush = AmsgPushCommon & {
299
+ messageKind: "reasoning";
300
+ reasoningContent: string;
301
+ title?: string;
302
+ contactName?: string;
303
+ avatarUrl?: string | null;
304
+ };
305
+ /**
306
+ * Tool invocation request emitted by an agentic-loop hook (`decision:
307
+ * 'tool-request'`). The client is expected to execute the tool and
308
+ * resume via the producer's `/continue` endpoint.
309
+ *
310
+ * `toolCalls` mirrors the OpenAI `choices[0].message.tool_calls`
311
+ * shape — left as `any`-equivalent so producers can passthrough
312
+ * whatever OpenAI-compatible upstream returned.
313
+ */
314
+ export type ToolRequestPush = AmsgPushCommon & {
315
+ messageKind: "tool_request";
316
+ toolCalls: Array<any>;
317
+ title?: string;
318
+ contactName?: string;
319
+ message?: string;
320
+ };
321
+ /**
322
+ * Producer-level error. Replaces the legacy
323
+ * `{ type: 'error', code: '...' }` envelope. `code` is a stable
324
+ * string; `iteration` is the agentic-loop iteration number when
325
+ * relevant (0 / absent otherwise).
326
+ */
327
+ export type ErrorPush = AmsgPushCommon & {
328
+ messageKind: "error";
329
+ code: string;
330
+ message: string;
331
+ iteration?: number;
332
+ };
333
+ /**
334
+ * Discriminated union of all pushes the SW can receive. TS consumers
335
+ * `switch` on `messageKind` and the compiler narrows automatically.
336
+ */
337
+ export type AmsgPush = ContentPush | ReasoningPush | ToolRequestPush | ErrorPush;
338
+ /**
339
+ * What the push carries. Fixed enum — packages must not add values.
340
+ */
341
+ export type MessageKind = "content" | "reasoning" | "tool_request" | "error";
342
+ /**
343
+ * How the push was produced. Fixed enum — packages must not add values.
344
+ */
345
+ export type MessageType = "instant" | "fixed" | "prompted" | "auto";
346
+ /**
347
+ * Which sub-package routed the push. Fixed enum — `'instant'` for
348
+ * `amsg-instant` (stateless one-shot), `'scheduled'` for any
349
+ * `amsg-server` output regardless of `messageType`. Packages must not
350
+ * add values.
351
+ */
352
+ export type PushSource = "instant" | "scheduled";
@@ -0,0 +1,352 @@
1
+ /**
2
+ * Build a {@link ContentPush}. Use this for legacy sentence-split
3
+ * bursts (set `messageIndex` 1-based + `totalMessages`) or for a
4
+ * single content push (omit both).
5
+ *
6
+ * @param {Object} args
7
+ * @param {MessageType} args.messageType
8
+ * @param {PushSource} args.source
9
+ * @param {string} args.messageId
10
+ * @param {string} args.sessionId
11
+ * @param {string} args.message
12
+ * @param {string} [args.timestamp] - Defaults to `new Date().toISOString()`.
13
+ * @param {string} [args.title]
14
+ * @param {string} [args.contactName]
15
+ * @param {string | null} [args.avatarUrl]
16
+ * @param {string} [args.messageSubtype]
17
+ * @param {number} [args.messageIndex]
18
+ * @param {number} [args.totalMessages]
19
+ * @param {string | null} [args.taskId]
20
+ * @param {Object} [args.metadata]
21
+ * @returns {ContentPush}
22
+ */
23
+ export function buildContentPush(args: {
24
+ messageType: MessageType;
25
+ source: PushSource;
26
+ messageId: string;
27
+ sessionId: string;
28
+ message: string;
29
+ timestamp?: string;
30
+ title?: string;
31
+ contactName?: string;
32
+ avatarUrl?: string | null;
33
+ messageSubtype?: string;
34
+ messageIndex?: number;
35
+ totalMessages?: number;
36
+ taskId?: string | null;
37
+ metadata?: any;
38
+ }): ContentPush;
39
+ /**
40
+ * Build a {@link ReasoningPush}. Producers emit this **before** any
41
+ * matching `ContentPush` burst when the LLM response carried a non-
42
+ * empty `reasoning_content`.
43
+ *
44
+ * Does NOT take `messageIndex` / `totalMessages` — reasoning is one
45
+ * push per LLM round.
46
+ *
47
+ * @param {Object} args
48
+ * @param {MessageType} args.messageType
49
+ * @param {PushSource} args.source
50
+ * @param {string} args.messageId
51
+ * @param {string} args.sessionId
52
+ * @param {string} args.reasoningContent
53
+ * @param {string} [args.timestamp]
54
+ * @param {string} [args.title]
55
+ * @param {string} [args.contactName]
56
+ * @param {string | null} [args.avatarUrl]
57
+ * @param {string} [args.messageSubtype]
58
+ * @param {Object} [args.metadata]
59
+ * @returns {ReasoningPush}
60
+ */
61
+ export function buildReasoningPush(args: {
62
+ messageType: MessageType;
63
+ source: PushSource;
64
+ messageId: string;
65
+ sessionId: string;
66
+ reasoningContent: string;
67
+ timestamp?: string;
68
+ title?: string;
69
+ contactName?: string;
70
+ avatarUrl?: string | null;
71
+ messageSubtype?: string;
72
+ metadata?: any;
73
+ }): ReasoningPush;
74
+ /**
75
+ * Build a {@link ToolRequestPush}. Caller is expected to executed
76
+ * tools client-side and resume via `/continue` (see `amsg-instant`
77
+ * README §Agentic Loop).
78
+ *
79
+ * @param {Object} args
80
+ * @param {MessageType} args.messageType
81
+ * @param {PushSource} args.source
82
+ * @param {string} args.messageId
83
+ * @param {string} args.sessionId
84
+ * @param {Array<Object>} args.toolCalls
85
+ * @param {string} [args.timestamp]
86
+ * @param {string} [args.title]
87
+ * @param {string} [args.contactName]
88
+ * @param {string} [args.message]
89
+ * @param {string} [args.messageSubtype]
90
+ * @param {Object} [args.metadata]
91
+ * @returns {ToolRequestPush}
92
+ */
93
+ export function buildToolRequestPush(args: {
94
+ messageType: MessageType;
95
+ source: PushSource;
96
+ messageId: string;
97
+ sessionId: string;
98
+ toolCalls: Array<any>;
99
+ timestamp?: string;
100
+ title?: string;
101
+ contactName?: string;
102
+ message?: string;
103
+ messageSubtype?: string;
104
+ metadata?: any;
105
+ }): ToolRequestPush;
106
+ /**
107
+ * Build an {@link ErrorPush}. Replaces the legacy
108
+ * `{ type: 'error', code: '...' }` envelope. The new shape carries
109
+ * the full common-fields set so the SW can route it through the
110
+ * same `messageKind` switch as the other three kinds.
111
+ *
112
+ * @param {Object} args
113
+ * @param {MessageType} args.messageType
114
+ * @param {PushSource} args.source
115
+ * @param {string} args.messageId
116
+ * @param {string} args.sessionId
117
+ * @param {string} args.code
118
+ * @param {string} args.message
119
+ * @param {string} [args.timestamp]
120
+ * @param {number} [args.iteration]
121
+ * @param {string} [args.messageSubtype]
122
+ * @param {Object} [args.metadata]
123
+ * @returns {ErrorPush}
124
+ */
125
+ export function buildErrorPush(args: {
126
+ messageType: MessageType;
127
+ source: PushSource;
128
+ messageId: string;
129
+ sessionId: string;
130
+ code: string;
131
+ message: string;
132
+ timestamp?: string;
133
+ iteration?: number;
134
+ messageSubtype?: string;
135
+ metadata?: any;
136
+ }): ErrorPush;
137
+ /**
138
+ * Type guard: returns true if the argument is a {@link ContentPush}.
139
+ *
140
+ * @param {unknown} value
141
+ * @returns {value is ContentPush}
142
+ */
143
+ export function isContentPush(value: unknown): value is ContentPush;
144
+ /**
145
+ * Type guard: returns true if the argument is a {@link ReasoningPush}.
146
+ *
147
+ * @param {unknown} value
148
+ * @returns {value is ReasoningPush}
149
+ */
150
+ export function isReasoningPush(value: unknown): value is ReasoningPush;
151
+ /**
152
+ * Type guard: returns true if the argument is a {@link ToolRequestPush}.
153
+ *
154
+ * @param {unknown} value
155
+ * @returns {value is ToolRequestPush}
156
+ */
157
+ export function isToolRequestPush(value: unknown): value is ToolRequestPush;
158
+ /**
159
+ * Type guard: returns true if the argument is an {@link ErrorPush}.
160
+ *
161
+ * @param {unknown} value
162
+ * @returns {value is ErrorPush}
163
+ */
164
+ export function isErrorPush(value: unknown): value is ErrorPush;
165
+ /**
166
+ * @rei-standard/amsg-shared
167
+ *
168
+ * Lowest layer of the ReiStandard Active Messaging ecosystem.
169
+ * Defines the three-axis push contract that `amsg-instant`,
170
+ * `amsg-server`, `amsg-sw`, and `amsg-client` all conform to.
171
+ *
172
+ * Three orthogonal axes:
173
+ * 1. messageType — how the push was produced (instant / fixed / prompted / auto)
174
+ * 2. messageSubtype — caller's business classification (free-form string)
175
+ * 3. messageKind — what the push carries (content / reasoning / tool_request / error)
176
+ *
177
+ * Zero runtime dependencies. The package is ESM/CJS dual-published and
178
+ * intentionally has no `dependencies:` entry — every other amsg sub-
179
+ * package depends on it, never the reverse.
180
+ *
181
+ * Types are expressed via JSDoc `@typedef` unions with literal-type
182
+ * discriminators so TS consumers can narrow on `messageKind`:
183
+ *
184
+ * if (push.messageKind === 'reasoning') {
185
+ * // TS knows: push is ReasoningPush, push.reasoningContent is string
186
+ * }
187
+ */
188
+ /**
189
+ * What the push carries. Fixed enum — packages must not add values.
190
+ *
191
+ * @typedef {'content' | 'reasoning' | 'tool_request' | 'error'} MessageKind
192
+ */
193
+ /**
194
+ * How the push was produced. Fixed enum — packages must not add values.
195
+ *
196
+ * @typedef {'instant' | 'fixed' | 'prompted' | 'auto'} MessageType
197
+ */
198
+ /**
199
+ * Which sub-package routed the push. Fixed enum — `'instant'` for
200
+ * `amsg-instant` (stateless one-shot), `'scheduled'` for any
201
+ * `amsg-server` output regardless of `messageType`. Packages must not
202
+ * add values.
203
+ *
204
+ * @typedef {'instant' | 'scheduled'} PushSource
205
+ */
206
+ /**
207
+ * Runtime constant mirroring the {@link MessageKind} type. Useful for
208
+ * switch statements that need to enumerate every kind:
209
+ *
210
+ * for (const kind of Object.values(MESSAGE_KIND)) { ... }
211
+ */
212
+ export const MESSAGE_KIND: Readonly<{
213
+ CONTENT: "content";
214
+ REASONING: "reasoning";
215
+ TOOL_REQUEST: "tool_request";
216
+ ERROR: "error";
217
+ }>;
218
+ /**
219
+ * Runtime constant mirroring the {@link MessageType} type.
220
+ */
221
+ export const MESSAGE_TYPE: Readonly<{
222
+ INSTANT: "instant";
223
+ FIXED: "fixed";
224
+ PROMPTED: "prompted";
225
+ AUTO: "auto";
226
+ }>;
227
+ /**
228
+ * Runtime constant mirroring the {@link PushSource} type.
229
+ */
230
+ export const PUSH_SOURCE: Readonly<{
231
+ INSTANT: "instant";
232
+ SCHEDULED: "scheduled";
233
+ }>;
234
+ /**
235
+ * Fields present on every push, regardless of kind. Discriminator
236
+ * fields (`messageKind`) and kind-specific fields live on the kind
237
+ * interfaces below.
238
+ *
239
+ * `metadata` is a passthrough namespace owned by the caller. Packages
240
+ * are forbidden from writing their own fields into `metadata` — any
241
+ * protocol-level data goes on top-level fields.
242
+ */
243
+ export type AmsgPushCommon = {
244
+ /**
245
+ * - How the push was produced.
246
+ */
247
+ messageType: MessageType;
248
+ /**
249
+ * - Which sub-package routed it.
250
+ */
251
+ source: PushSource;
252
+ /**
253
+ * - Unique per push. Format owned by the producer.
254
+ */
255
+ messageId: string;
256
+ /**
257
+ * - Shared across all pushes from one LLM round (reasoning + content) and across iterations of a single agentic-loop request.
258
+ */
259
+ sessionId: string;
260
+ /**
261
+ * - ISO 8601 timestamp at producer.
262
+ */
263
+ timestamp: string;
264
+ /**
265
+ * - Caller-defined business namespace. Defaults to 'chat' at producers.
266
+ */
267
+ messageSubtype?: string;
268
+ /**
269
+ * - Caller passthrough. Packages MUST NOT write here.
270
+ */
271
+ metadata?: any;
272
+ };
273
+ /**
274
+ * Final user-facing content. Sentence-split bursts of N use
275
+ * `messageIndex` (1-based) + `totalMessages` so the client can
276
+ * reassemble or animate.
277
+ */
278
+ export type ContentPush = AmsgPushCommon & {
279
+ messageKind: "content";
280
+ message: string;
281
+ title?: string;
282
+ contactName?: string;
283
+ avatarUrl?: string | null;
284
+ messageIndex?: number;
285
+ totalMessages?: number;
286
+ taskId?: string | null;
287
+ };
288
+ /**
289
+ * LLM "meta-thinking" — `choices[0].message.reasoning_content` lifted
290
+ * out of the upstream response into its own push. Emitted **before**
291
+ * the matching {@link ContentPush} burst when present and non-empty.
292
+ *
293
+ * Intentionally does NOT carry `messageIndex` / `totalMessages` —
294
+ * reasoning is a single push per LLM round, never a split-burst.
295
+ * That's why those fields are absent at the type level rather than
296
+ * `optional` (which would leave callers wondering when they're set).
297
+ */
298
+ export type ReasoningPush = AmsgPushCommon & {
299
+ messageKind: "reasoning";
300
+ reasoningContent: string;
301
+ title?: string;
302
+ contactName?: string;
303
+ avatarUrl?: string | null;
304
+ };
305
+ /**
306
+ * Tool invocation request emitted by an agentic-loop hook (`decision:
307
+ * 'tool-request'`). The client is expected to execute the tool and
308
+ * resume via the producer's `/continue` endpoint.
309
+ *
310
+ * `toolCalls` mirrors the OpenAI `choices[0].message.tool_calls`
311
+ * shape — left as `any`-equivalent so producers can passthrough
312
+ * whatever OpenAI-compatible upstream returned.
313
+ */
314
+ export type ToolRequestPush = AmsgPushCommon & {
315
+ messageKind: "tool_request";
316
+ toolCalls: Array<any>;
317
+ title?: string;
318
+ contactName?: string;
319
+ message?: string;
320
+ };
321
+ /**
322
+ * Producer-level error. Replaces the legacy
323
+ * `{ type: 'error', code: '...' }` envelope. `code` is a stable
324
+ * string; `iteration` is the agentic-loop iteration number when
325
+ * relevant (0 / absent otherwise).
326
+ */
327
+ export type ErrorPush = AmsgPushCommon & {
328
+ messageKind: "error";
329
+ code: string;
330
+ message: string;
331
+ iteration?: number;
332
+ };
333
+ /**
334
+ * Discriminated union of all pushes the SW can receive. TS consumers
335
+ * `switch` on `messageKind` and the compiler narrows automatically.
336
+ */
337
+ export type AmsgPush = ContentPush | ReasoningPush | ToolRequestPush | ErrorPush;
338
+ /**
339
+ * What the push carries. Fixed enum — packages must not add values.
340
+ */
341
+ export type MessageKind = "content" | "reasoning" | "tool_request" | "error";
342
+ /**
343
+ * How the push was produced. Fixed enum — packages must not add values.
344
+ */
345
+ export type MessageType = "instant" | "fixed" | "prompted" | "auto";
346
+ /**
347
+ * Which sub-package routed the push. Fixed enum — `'instant'` for
348
+ * `amsg-instant` (stateless one-shot), `'scheduled'` for any
349
+ * `amsg-server` output regardless of `messageType`. Packages must not
350
+ * add values.
351
+ */
352
+ export type PushSource = "instant" | "scheduled";
package/dist/index.mjs ADDED
@@ -0,0 +1,150 @@
1
+ // src/index.js
2
+ var MESSAGE_KIND = Object.freeze({
3
+ CONTENT: "content",
4
+ REASONING: "reasoning",
5
+ TOOL_REQUEST: "tool_request",
6
+ ERROR: "error"
7
+ });
8
+ var MESSAGE_TYPE = Object.freeze({
9
+ INSTANT: "instant",
10
+ FIXED: "fixed",
11
+ PROMPTED: "prompted",
12
+ AUTO: "auto"
13
+ });
14
+ var PUSH_SOURCE = Object.freeze({
15
+ INSTANT: "instant",
16
+ SCHEDULED: "scheduled"
17
+ });
18
+ function requireField(kind, field, value) {
19
+ if (value === void 0 || value === null || value === "") {
20
+ throw new Error(`[amsg-shared] ${kind}: '${field}' is required`);
21
+ }
22
+ }
23
+ function buildContentPush(args) {
24
+ requireField("ContentPush", "messageType", args.messageType);
25
+ requireField("ContentPush", "source", args.source);
26
+ requireField("ContentPush", "messageId", args.messageId);
27
+ requireField("ContentPush", "sessionId", args.sessionId);
28
+ if (typeof args.message !== "string") {
29
+ throw new Error("[amsg-shared] ContentPush: 'message' must be a string");
30
+ }
31
+ const push = {
32
+ messageKind: "content",
33
+ messageType: args.messageType,
34
+ source: args.source,
35
+ messageId: args.messageId,
36
+ sessionId: args.sessionId,
37
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
38
+ message: args.message
39
+ };
40
+ if (args.title !== void 0) push.title = args.title;
41
+ if (args.contactName !== void 0) push.contactName = args.contactName;
42
+ if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
43
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
44
+ if (args.messageIndex !== void 0) push.messageIndex = args.messageIndex;
45
+ if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
46
+ if (args.taskId !== void 0) push.taskId = args.taskId;
47
+ if (args.metadata !== void 0) push.metadata = args.metadata;
48
+ return push;
49
+ }
50
+ function buildReasoningPush(args) {
51
+ requireField("ReasoningPush", "messageType", args.messageType);
52
+ requireField("ReasoningPush", "source", args.source);
53
+ requireField("ReasoningPush", "messageId", args.messageId);
54
+ requireField("ReasoningPush", "sessionId", args.sessionId);
55
+ if (typeof args.reasoningContent !== "string" || !args.reasoningContent) {
56
+ throw new Error("[amsg-shared] ReasoningPush: 'reasoningContent' must be a non-empty string");
57
+ }
58
+ const push = {
59
+ messageKind: "reasoning",
60
+ messageType: args.messageType,
61
+ source: args.source,
62
+ messageId: args.messageId,
63
+ sessionId: args.sessionId,
64
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
65
+ reasoningContent: args.reasoningContent
66
+ };
67
+ if (args.title !== void 0) push.title = args.title;
68
+ if (args.contactName !== void 0) push.contactName = args.contactName;
69
+ if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
70
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
71
+ if (args.metadata !== void 0) push.metadata = args.metadata;
72
+ return push;
73
+ }
74
+ function buildToolRequestPush(args) {
75
+ requireField("ToolRequestPush", "messageType", args.messageType);
76
+ requireField("ToolRequestPush", "source", args.source);
77
+ requireField("ToolRequestPush", "messageId", args.messageId);
78
+ requireField("ToolRequestPush", "sessionId", args.sessionId);
79
+ if (!Array.isArray(args.toolCalls) || args.toolCalls.length === 0) {
80
+ throw new Error("[amsg-shared] ToolRequestPush: 'toolCalls' must be a non-empty array");
81
+ }
82
+ const push = {
83
+ messageKind: "tool_request",
84
+ messageType: args.messageType,
85
+ source: args.source,
86
+ messageId: args.messageId,
87
+ sessionId: args.sessionId,
88
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
89
+ toolCalls: args.toolCalls
90
+ };
91
+ if (args.title !== void 0) push.title = args.title;
92
+ if (args.contactName !== void 0) push.contactName = args.contactName;
93
+ if (args.message !== void 0) push.message = args.message;
94
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
95
+ if (args.metadata !== void 0) push.metadata = args.metadata;
96
+ return push;
97
+ }
98
+ function buildErrorPush(args) {
99
+ requireField("ErrorPush", "messageType", args.messageType);
100
+ requireField("ErrorPush", "source", args.source);
101
+ requireField("ErrorPush", "messageId", args.messageId);
102
+ requireField("ErrorPush", "sessionId", args.sessionId);
103
+ requireField("ErrorPush", "code", args.code);
104
+ if (typeof args.message !== "string") {
105
+ throw new Error("[amsg-shared] ErrorPush: 'message' must be a string");
106
+ }
107
+ const push = {
108
+ messageKind: "error",
109
+ messageType: args.messageType,
110
+ source: args.source,
111
+ messageId: args.messageId,
112
+ sessionId: args.sessionId,
113
+ timestamp: args.timestamp || (/* @__PURE__ */ new Date()).toISOString(),
114
+ code: args.code,
115
+ message: args.message
116
+ };
117
+ if (args.iteration !== void 0) push.iteration = args.iteration;
118
+ if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
119
+ if (args.metadata !== void 0) push.metadata = args.metadata;
120
+ return push;
121
+ }
122
+ function isContentPush(value) {
123
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
124
+ value.messageKind === "content";
125
+ }
126
+ function isReasoningPush(value) {
127
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
128
+ value.messageKind === "reasoning";
129
+ }
130
+ function isToolRequestPush(value) {
131
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
132
+ value.messageKind === "tool_request";
133
+ }
134
+ function isErrorPush(value) {
135
+ return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
136
+ value.messageKind === "error";
137
+ }
138
+ export {
139
+ MESSAGE_KIND,
140
+ MESSAGE_TYPE,
141
+ PUSH_SOURCE,
142
+ buildContentPush,
143
+ buildErrorPush,
144
+ buildReasoningPush,
145
+ buildToolRequestPush,
146
+ isContentPush,
147
+ isErrorPush,
148
+ isReasoningPush,
149
+ isToolRequestPush
150
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@rei-standard/amsg-shared",
3
+ "version": "0.1.0-next.0",
4
+ "description": "ReiStandard Active Messaging shared types and push builders — the lowest layer (no deps on other amsg packages)",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/Tosd0/ReiStandard",
8
+ "directory": "packages/rei-standard-amsg/shared"
9
+ },
10
+ "license": "MIT",
11
+ "type": "module",
12
+ "sideEffects": false,
13
+ "publishConfig": {
14
+ "access": "public"
15
+ },
16
+ "main": "./dist/index.cjs",
17
+ "module": "./dist/index.mjs",
18
+ "types": "./dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.mjs",
23
+ "require": "./dist/index.cjs"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "scripts": {
30
+ "build": "tsup && tsc -p tsconfig.json && node -e \"require('fs').copyFileSync('dist/index.d.ts', 'dist/index.d.cts')\"",
31
+ "test": "node --test test/*.test.mjs"
32
+ },
33
+ "engines": {
34
+ "node": ">=20"
35
+ },
36
+ "devDependencies": {
37
+ "tsup": "^8.0.0",
38
+ "typescript": "^5.0.0"
39
+ }
40
+ }