@rei-standard/amsg-shared 0.1.0-next.0 → 0.1.0-next.2
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 +33 -0
- package/dist/index.d.cts +74 -6
- package/dist/index.d.ts +74 -6
- package/dist/index.mjs +33 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ __export(src_exports, {
|
|
|
26
26
|
buildErrorPush: () => buildErrorPush,
|
|
27
27
|
buildReasoningPush: () => buildReasoningPush,
|
|
28
28
|
buildToolRequestPush: () => buildToolRequestPush,
|
|
29
|
+
chunkReasoningByUtf8Bytes: () => chunkReasoningByUtf8Bytes,
|
|
29
30
|
isContentPush: () => isContentPush,
|
|
30
31
|
isErrorPush: () => isErrorPush,
|
|
31
32
|
isReasoningPush: () => isReasoningPush,
|
|
@@ -101,6 +102,10 @@ function buildReasoningPush(args) {
|
|
|
101
102
|
if (args.contactName !== void 0) push.contactName = args.contactName;
|
|
102
103
|
if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
|
|
103
104
|
if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
|
|
105
|
+
if (args.messageIndex !== void 0) push.messageIndex = args.messageIndex;
|
|
106
|
+
if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
|
|
107
|
+
if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
|
|
108
|
+
if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
|
|
104
109
|
if (args.metadata !== void 0) push.metadata = args.metadata;
|
|
105
110
|
return push;
|
|
106
111
|
}
|
|
@@ -168,3 +173,31 @@ function isErrorPush(value) {
|
|
|
168
173
|
return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
|
|
169
174
|
value.messageKind === "error";
|
|
170
175
|
}
|
|
176
|
+
var REASONING_CHUNK_ENCODER = new TextEncoder();
|
|
177
|
+
var REASONING_CHUNK_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
178
|
+
function chunkReasoningByUtf8Bytes(text, maxBytes) {
|
|
179
|
+
if (typeof text !== "string") {
|
|
180
|
+
throw new TypeError("[amsg-shared] chunkReasoningByUtf8Bytes: text must be a string");
|
|
181
|
+
}
|
|
182
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 4) {
|
|
183
|
+
throw new RangeError(
|
|
184
|
+
"[amsg-shared] chunkReasoningByUtf8Bytes: maxBytes must be an integer \u2265 4 (UTF-8 max codepoint width)"
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
if (text.length === 0) return [];
|
|
188
|
+
const bytes = REASONING_CHUNK_ENCODER.encode(text);
|
|
189
|
+
if (bytes.byteLength <= maxBytes) return [text];
|
|
190
|
+
const chunks = [];
|
|
191
|
+
let start = 0;
|
|
192
|
+
while (start < bytes.byteLength) {
|
|
193
|
+
let end = Math.min(start + maxBytes, bytes.byteLength);
|
|
194
|
+
if (end < bytes.byteLength) {
|
|
195
|
+
while (end > start && (bytes[end] & 192) === 128) {
|
|
196
|
+
end--;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
chunks.push(REASONING_CHUNK_DECODER.decode(bytes.subarray(start, end)));
|
|
200
|
+
start = end;
|
|
201
|
+
}
|
|
202
|
+
return chunks;
|
|
203
|
+
}
|
package/dist/index.d.cts
CHANGED
|
@@ -41,8 +41,16 @@ export function buildContentPush(args: {
|
|
|
41
41
|
* matching `ContentPush` burst when the LLM response carried a non-
|
|
42
42
|
* empty `reasoning_content`.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Two optional multi-part axes (both omitted from wire when the part
|
|
45
|
+
* count is 1, so single-shot reasoning stays byte-for-byte compatible):
|
|
46
|
+
*
|
|
47
|
+
* - `messageIndex` / `totalMessages` — semantic splitter (sentence
|
|
48
|
+
* regex) produced multiple segments.
|
|
49
|
+
* - `chunkIndex` / `totalChunks` — byte splitter (UTF-8 payload-limit
|
|
50
|
+
* workaround) sliced a single segment across multiple pushes.
|
|
51
|
+
*
|
|
52
|
+
* Both can be set together when a sentence-split segment is itself
|
|
53
|
+
* oversized. See README §"Reasoning chunking".
|
|
46
54
|
*
|
|
47
55
|
* @param {Object} args
|
|
48
56
|
* @param {MessageType} args.messageType
|
|
@@ -55,6 +63,10 @@ export function buildContentPush(args: {
|
|
|
55
63
|
* @param {string} [args.contactName]
|
|
56
64
|
* @param {string | null} [args.avatarUrl]
|
|
57
65
|
* @param {string} [args.messageSubtype]
|
|
66
|
+
* @param {number} [args.messageIndex]
|
|
67
|
+
* @param {number} [args.totalMessages]
|
|
68
|
+
* @param {number} [args.chunkIndex]
|
|
69
|
+
* @param {number} [args.totalChunks]
|
|
58
70
|
* @param {Object} [args.metadata]
|
|
59
71
|
* @returns {ReasoningPush}
|
|
60
72
|
*/
|
|
@@ -69,6 +81,10 @@ export function buildReasoningPush(args: {
|
|
|
69
81
|
contactName?: string;
|
|
70
82
|
avatarUrl?: string | null;
|
|
71
83
|
messageSubtype?: string;
|
|
84
|
+
messageIndex?: number;
|
|
85
|
+
totalMessages?: number;
|
|
86
|
+
chunkIndex?: number;
|
|
87
|
+
totalChunks?: number;
|
|
72
88
|
metadata?: any;
|
|
73
89
|
}): ReasoningPush;
|
|
74
90
|
/**
|
|
@@ -162,6 +178,40 @@ export function isToolRequestPush(value: unknown): value is ToolRequestPush;
|
|
|
162
178
|
* @returns {value is ErrorPush}
|
|
163
179
|
*/
|
|
164
180
|
export function isErrorPush(value: unknown): value is ErrorPush;
|
|
181
|
+
/**
|
|
182
|
+
* Slice a string into UTF-8 byte chunks no larger than `maxBytes`,
|
|
183
|
+
* always cutting at codepoint boundaries (never inside a multi-byte
|
|
184
|
+
* char). Designed for the {@link ReasoningPush} byte-chunking path
|
|
185
|
+
* in amsg-instant — producers facing the ~3 KB Web Push payload
|
|
186
|
+
* limit slice oversized reasoning into N pushes with
|
|
187
|
+
* `chunkIndex` / `totalChunks`, the SW reassembles by concat.
|
|
188
|
+
*
|
|
189
|
+
* Algorithm: TextEncoder → Uint8Array → backward scan from each
|
|
190
|
+
* candidate cut index until the byte is a UTF-8 lead byte (any byte
|
|
191
|
+
* where `(b & 0xC0) !== 0x80`; continuation bytes are `0b10xxxxxx`).
|
|
192
|
+
* TextDecoder turns each slice back into a JS string.
|
|
193
|
+
*
|
|
194
|
+
* chunkReasoningByUtf8Bytes('A寿B', 4) → ['A寿', 'B'] // '寿' = 3 B,
|
|
195
|
+
* // cut at safe edge
|
|
196
|
+
*
|
|
197
|
+
* Constraints:
|
|
198
|
+
* - `maxBytes` MUST be ≥ 4 (UTF-8 codepoints can be up to 4 bytes;
|
|
199
|
+
* any smaller threshold has no valid cut point for a 4-byte char
|
|
200
|
+
* and is also operationally nonsensical). Throws `RangeError`
|
|
201
|
+
* otherwise.
|
|
202
|
+
* - Empty `text` → `[]` (caller can check `.length === 0`).
|
|
203
|
+
* - `text` whose total UTF-8 byte length ≤ `maxBytes` → `[text]`
|
|
204
|
+
* (no chunking).
|
|
205
|
+
* - `text` MUST be a string. Non-string throws `TypeError`.
|
|
206
|
+
*
|
|
207
|
+
* Joining the result `chunks.join('')` is guaranteed to equal the
|
|
208
|
+
* input `text` (no data loss, no extra whitespace).
|
|
209
|
+
*
|
|
210
|
+
* @param {string} text
|
|
211
|
+
* @param {number} maxBytes
|
|
212
|
+
* @returns {string[]}
|
|
213
|
+
*/
|
|
214
|
+
export function chunkReasoningByUtf8Bytes(text: string, maxBytes: number): string[];
|
|
165
215
|
/**
|
|
166
216
|
* @rei-standard/amsg-shared
|
|
167
217
|
*
|
|
@@ -290,10 +340,24 @@ export type ContentPush = AmsgPushCommon & {
|
|
|
290
340
|
* out of the upstream response into its own push. Emitted **before**
|
|
291
341
|
* the matching {@link ContentPush} burst when present and non-empty.
|
|
292
342
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
343
|
+
* Reasoning carries two orthogonal "multi-part" axes, both optional —
|
|
344
|
+
* they are *omitted* when the part count is 1 so the wire stays
|
|
345
|
+
* byte-for-byte compatible with single-shot ReasoningPush callers:
|
|
346
|
+
*
|
|
347
|
+
* - `messageIndex` / `totalMessages` — set when a semantic
|
|
348
|
+
* splitter (`reasoningSplitPattern` in amsg-instant) has cut the
|
|
349
|
+
* reasoning into multiple sentences for typing-bubble UX.
|
|
350
|
+
*
|
|
351
|
+
* - `chunkIndex` / `totalChunks` — set when a single segment was
|
|
352
|
+
* too large for the Web Push payload limit and the producer had
|
|
353
|
+
* to slice it across multiple pushes at UTF-8 byte boundaries.
|
|
354
|
+
* Transport-only; SW reassembles the original `reasoningContent`
|
|
355
|
+
* by sorting on `chunkIndex` within a `(sessionId, messageIndex)`
|
|
356
|
+
* bucket. See `chunkReasoningByUtf8Bytes` for the safe-edge
|
|
357
|
+
* splitter helper.
|
|
358
|
+
*
|
|
359
|
+
* Both axes can coexist on the same push when a sentence-split
|
|
360
|
+
* segment is itself oversized.
|
|
297
361
|
*/
|
|
298
362
|
export type ReasoningPush = AmsgPushCommon & {
|
|
299
363
|
messageKind: "reasoning";
|
|
@@ -301,6 +365,10 @@ export type ReasoningPush = AmsgPushCommon & {
|
|
|
301
365
|
title?: string;
|
|
302
366
|
contactName?: string;
|
|
303
367
|
avatarUrl?: string | null;
|
|
368
|
+
messageIndex?: number;
|
|
369
|
+
totalMessages?: number;
|
|
370
|
+
chunkIndex?: number;
|
|
371
|
+
totalChunks?: number;
|
|
304
372
|
};
|
|
305
373
|
/**
|
|
306
374
|
* Tool invocation request emitted by an agentic-loop hook (`decision:
|
package/dist/index.d.ts
CHANGED
|
@@ -41,8 +41,16 @@ export function buildContentPush(args: {
|
|
|
41
41
|
* matching `ContentPush` burst when the LLM response carried a non-
|
|
42
42
|
* empty `reasoning_content`.
|
|
43
43
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
44
|
+
* Two optional multi-part axes (both omitted from wire when the part
|
|
45
|
+
* count is 1, so single-shot reasoning stays byte-for-byte compatible):
|
|
46
|
+
*
|
|
47
|
+
* - `messageIndex` / `totalMessages` — semantic splitter (sentence
|
|
48
|
+
* regex) produced multiple segments.
|
|
49
|
+
* - `chunkIndex` / `totalChunks` — byte splitter (UTF-8 payload-limit
|
|
50
|
+
* workaround) sliced a single segment across multiple pushes.
|
|
51
|
+
*
|
|
52
|
+
* Both can be set together when a sentence-split segment is itself
|
|
53
|
+
* oversized. See README §"Reasoning chunking".
|
|
46
54
|
*
|
|
47
55
|
* @param {Object} args
|
|
48
56
|
* @param {MessageType} args.messageType
|
|
@@ -55,6 +63,10 @@ export function buildContentPush(args: {
|
|
|
55
63
|
* @param {string} [args.contactName]
|
|
56
64
|
* @param {string | null} [args.avatarUrl]
|
|
57
65
|
* @param {string} [args.messageSubtype]
|
|
66
|
+
* @param {number} [args.messageIndex]
|
|
67
|
+
* @param {number} [args.totalMessages]
|
|
68
|
+
* @param {number} [args.chunkIndex]
|
|
69
|
+
* @param {number} [args.totalChunks]
|
|
58
70
|
* @param {Object} [args.metadata]
|
|
59
71
|
* @returns {ReasoningPush}
|
|
60
72
|
*/
|
|
@@ -69,6 +81,10 @@ export function buildReasoningPush(args: {
|
|
|
69
81
|
contactName?: string;
|
|
70
82
|
avatarUrl?: string | null;
|
|
71
83
|
messageSubtype?: string;
|
|
84
|
+
messageIndex?: number;
|
|
85
|
+
totalMessages?: number;
|
|
86
|
+
chunkIndex?: number;
|
|
87
|
+
totalChunks?: number;
|
|
72
88
|
metadata?: any;
|
|
73
89
|
}): ReasoningPush;
|
|
74
90
|
/**
|
|
@@ -162,6 +178,40 @@ export function isToolRequestPush(value: unknown): value is ToolRequestPush;
|
|
|
162
178
|
* @returns {value is ErrorPush}
|
|
163
179
|
*/
|
|
164
180
|
export function isErrorPush(value: unknown): value is ErrorPush;
|
|
181
|
+
/**
|
|
182
|
+
* Slice a string into UTF-8 byte chunks no larger than `maxBytes`,
|
|
183
|
+
* always cutting at codepoint boundaries (never inside a multi-byte
|
|
184
|
+
* char). Designed for the {@link ReasoningPush} byte-chunking path
|
|
185
|
+
* in amsg-instant — producers facing the ~3 KB Web Push payload
|
|
186
|
+
* limit slice oversized reasoning into N pushes with
|
|
187
|
+
* `chunkIndex` / `totalChunks`, the SW reassembles by concat.
|
|
188
|
+
*
|
|
189
|
+
* Algorithm: TextEncoder → Uint8Array → backward scan from each
|
|
190
|
+
* candidate cut index until the byte is a UTF-8 lead byte (any byte
|
|
191
|
+
* where `(b & 0xC0) !== 0x80`; continuation bytes are `0b10xxxxxx`).
|
|
192
|
+
* TextDecoder turns each slice back into a JS string.
|
|
193
|
+
*
|
|
194
|
+
* chunkReasoningByUtf8Bytes('A寿B', 4) → ['A寿', 'B'] // '寿' = 3 B,
|
|
195
|
+
* // cut at safe edge
|
|
196
|
+
*
|
|
197
|
+
* Constraints:
|
|
198
|
+
* - `maxBytes` MUST be ≥ 4 (UTF-8 codepoints can be up to 4 bytes;
|
|
199
|
+
* any smaller threshold has no valid cut point for a 4-byte char
|
|
200
|
+
* and is also operationally nonsensical). Throws `RangeError`
|
|
201
|
+
* otherwise.
|
|
202
|
+
* - Empty `text` → `[]` (caller can check `.length === 0`).
|
|
203
|
+
* - `text` whose total UTF-8 byte length ≤ `maxBytes` → `[text]`
|
|
204
|
+
* (no chunking).
|
|
205
|
+
* - `text` MUST be a string. Non-string throws `TypeError`.
|
|
206
|
+
*
|
|
207
|
+
* Joining the result `chunks.join('')` is guaranteed to equal the
|
|
208
|
+
* input `text` (no data loss, no extra whitespace).
|
|
209
|
+
*
|
|
210
|
+
* @param {string} text
|
|
211
|
+
* @param {number} maxBytes
|
|
212
|
+
* @returns {string[]}
|
|
213
|
+
*/
|
|
214
|
+
export function chunkReasoningByUtf8Bytes(text: string, maxBytes: number): string[];
|
|
165
215
|
/**
|
|
166
216
|
* @rei-standard/amsg-shared
|
|
167
217
|
*
|
|
@@ -290,10 +340,24 @@ export type ContentPush = AmsgPushCommon & {
|
|
|
290
340
|
* out of the upstream response into its own push. Emitted **before**
|
|
291
341
|
* the matching {@link ContentPush} burst when present and non-empty.
|
|
292
342
|
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
*
|
|
343
|
+
* Reasoning carries two orthogonal "multi-part" axes, both optional —
|
|
344
|
+
* they are *omitted* when the part count is 1 so the wire stays
|
|
345
|
+
* byte-for-byte compatible with single-shot ReasoningPush callers:
|
|
346
|
+
*
|
|
347
|
+
* - `messageIndex` / `totalMessages` — set when a semantic
|
|
348
|
+
* splitter (`reasoningSplitPattern` in amsg-instant) has cut the
|
|
349
|
+
* reasoning into multiple sentences for typing-bubble UX.
|
|
350
|
+
*
|
|
351
|
+
* - `chunkIndex` / `totalChunks` — set when a single segment was
|
|
352
|
+
* too large for the Web Push payload limit and the producer had
|
|
353
|
+
* to slice it across multiple pushes at UTF-8 byte boundaries.
|
|
354
|
+
* Transport-only; SW reassembles the original `reasoningContent`
|
|
355
|
+
* by sorting on `chunkIndex` within a `(sessionId, messageIndex)`
|
|
356
|
+
* bucket. See `chunkReasoningByUtf8Bytes` for the safe-edge
|
|
357
|
+
* splitter helper.
|
|
358
|
+
*
|
|
359
|
+
* Both axes can coexist on the same push when a sentence-split
|
|
360
|
+
* segment is itself oversized.
|
|
297
361
|
*/
|
|
298
362
|
export type ReasoningPush = AmsgPushCommon & {
|
|
299
363
|
messageKind: "reasoning";
|
|
@@ -301,6 +365,10 @@ export type ReasoningPush = AmsgPushCommon & {
|
|
|
301
365
|
title?: string;
|
|
302
366
|
contactName?: string;
|
|
303
367
|
avatarUrl?: string | null;
|
|
368
|
+
messageIndex?: number;
|
|
369
|
+
totalMessages?: number;
|
|
370
|
+
chunkIndex?: number;
|
|
371
|
+
totalChunks?: number;
|
|
304
372
|
};
|
|
305
373
|
/**
|
|
306
374
|
* Tool invocation request emitted by an agentic-loop hook (`decision:
|
package/dist/index.mjs
CHANGED
|
@@ -68,6 +68,10 @@ function buildReasoningPush(args) {
|
|
|
68
68
|
if (args.contactName !== void 0) push.contactName = args.contactName;
|
|
69
69
|
if (args.avatarUrl !== void 0) push.avatarUrl = args.avatarUrl;
|
|
70
70
|
if (args.messageSubtype !== void 0) push.messageSubtype = args.messageSubtype;
|
|
71
|
+
if (args.messageIndex !== void 0) push.messageIndex = args.messageIndex;
|
|
72
|
+
if (args.totalMessages !== void 0) push.totalMessages = args.totalMessages;
|
|
73
|
+
if (args.chunkIndex !== void 0) push.chunkIndex = args.chunkIndex;
|
|
74
|
+
if (args.totalChunks !== void 0) push.totalChunks = args.totalChunks;
|
|
71
75
|
if (args.metadata !== void 0) push.metadata = args.metadata;
|
|
72
76
|
return push;
|
|
73
77
|
}
|
|
@@ -135,6 +139,34 @@ function isErrorPush(value) {
|
|
|
135
139
|
return !!value && typeof value === "object" && /** @type {{messageKind?: unknown}} */
|
|
136
140
|
value.messageKind === "error";
|
|
137
141
|
}
|
|
142
|
+
var REASONING_CHUNK_ENCODER = new TextEncoder();
|
|
143
|
+
var REASONING_CHUNK_DECODER = new TextDecoder("utf-8", { fatal: true });
|
|
144
|
+
function chunkReasoningByUtf8Bytes(text, maxBytes) {
|
|
145
|
+
if (typeof text !== "string") {
|
|
146
|
+
throw new TypeError("[amsg-shared] chunkReasoningByUtf8Bytes: text must be a string");
|
|
147
|
+
}
|
|
148
|
+
if (!Number.isInteger(maxBytes) || maxBytes < 4) {
|
|
149
|
+
throw new RangeError(
|
|
150
|
+
"[amsg-shared] chunkReasoningByUtf8Bytes: maxBytes must be an integer \u2265 4 (UTF-8 max codepoint width)"
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
if (text.length === 0) return [];
|
|
154
|
+
const bytes = REASONING_CHUNK_ENCODER.encode(text);
|
|
155
|
+
if (bytes.byteLength <= maxBytes) return [text];
|
|
156
|
+
const chunks = [];
|
|
157
|
+
let start = 0;
|
|
158
|
+
while (start < bytes.byteLength) {
|
|
159
|
+
let end = Math.min(start + maxBytes, bytes.byteLength);
|
|
160
|
+
if (end < bytes.byteLength) {
|
|
161
|
+
while (end > start && (bytes[end] & 192) === 128) {
|
|
162
|
+
end--;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
chunks.push(REASONING_CHUNK_DECODER.decode(bytes.subarray(start, end)));
|
|
166
|
+
start = end;
|
|
167
|
+
}
|
|
168
|
+
return chunks;
|
|
169
|
+
}
|
|
138
170
|
export {
|
|
139
171
|
MESSAGE_KIND,
|
|
140
172
|
MESSAGE_TYPE,
|
|
@@ -143,6 +175,7 @@ export {
|
|
|
143
175
|
buildErrorPush,
|
|
144
176
|
buildReasoningPush,
|
|
145
177
|
buildToolRequestPush,
|
|
178
|
+
chunkReasoningByUtf8Bytes,
|
|
146
179
|
isContentPush,
|
|
147
180
|
isErrorPush,
|
|
148
181
|
isReasoningPush,
|
package/package.json
CHANGED