@rei-standard/amsg-shared 0.2.0 → 0.3.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 +5 -5
- package/dist/index.cjs +70 -1
- package/dist/index.d.cts +67 -15
- package/dist/index.d.ts +67 -15
- package/dist/index.mjs +70 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @rei-standard/amsg-shared
|
|
2
2
|
|
|
3
|
-
Lowest layer of the ReiStandard Active Messaging
|
|
4
|
-
the **
|
|
3
|
+
Lowest layer of the ReiStandard Active Messaging stack. Defines
|
|
4
|
+
the **push schema** that `amsg-instant`, `amsg-server`,
|
|
5
5
|
`amsg-sw`, and `amsg-client` all conform to.
|
|
6
6
|
|
|
7
7
|
Zero runtime deps. Does **not** depend on any other amsg package —
|
|
@@ -9,9 +9,9 @@ every other amsg sub-package depends on this one, never the reverse.
|
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
##
|
|
12
|
+
## Push schema
|
|
13
13
|
|
|
14
|
-
A single push is described by three
|
|
14
|
+
A single push is described by three independent dimensions:
|
|
15
15
|
|
|
16
16
|
| Axis | Field | Values | Defined by |
|
|
17
17
|
|----------------|-------------------|-------------------------------------------------------|--------------------|
|
|
@@ -22,7 +22,7 @@ A single push is described by three orthogonal axes:
|
|
|
22
22
|
`messageType` answers **how this push was produced** (one-shot
|
|
23
23
|
`instant` worker, scheduled `fixed` ping, AI-`prompted` reply, fully
|
|
24
24
|
`auto`-generated cadence). `messageKind` answers **what it carries**.
|
|
25
|
-
The two are intentionally
|
|
25
|
+
The two are intentionally independent: any `messageType` can carry any
|
|
26
26
|
`messageKind`.
|
|
27
27
|
|
|
28
28
|
There is also `source: 'instant' | 'scheduled'` — the **routing
|
package/dist/index.cjs
CHANGED
|
@@ -19,6 +19,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
19
19
|
// src/index.js
|
|
20
20
|
var src_exports = {};
|
|
21
21
|
__export(src_exports, {
|
|
22
|
+
AVATAR_URL_MAX_LENGTH: () => AVATAR_URL_MAX_LENGTH,
|
|
22
23
|
MESSAGE_KIND: () => MESSAGE_KIND,
|
|
23
24
|
MESSAGE_TYPE: () => MESSAGE_TYPE,
|
|
24
25
|
PUSH_SOURCE: () => PUSH_SOURCE,
|
|
@@ -33,7 +34,12 @@ __export(src_exports, {
|
|
|
33
34
|
isErrorPush: () => isErrorPush,
|
|
34
35
|
isReasoningPush: () => isReasoningPush,
|
|
35
36
|
isToolRequestPush: () => isToolRequestPush,
|
|
36
|
-
|
|
37
|
+
isValidUrl: () => isValidUrl,
|
|
38
|
+
normalizeVapidSubject: () => normalizeVapidSubject,
|
|
39
|
+
readReasoningContent: () => readReasoningContent,
|
|
40
|
+
stripReasoningTags: () => stripReasoningTags,
|
|
41
|
+
toUint8: () => toUint8,
|
|
42
|
+
validateAvatarUrl: () => validateAvatarUrl
|
|
37
43
|
});
|
|
38
44
|
module.exports = __toCommonJS(src_exports);
|
|
39
45
|
var MESSAGE_KIND = Object.freeze({
|
|
@@ -264,3 +270,66 @@ function concatBytes(...chunks) {
|
|
|
264
270
|
}
|
|
265
271
|
return out;
|
|
266
272
|
}
|
|
273
|
+
function isValidUrl(value) {
|
|
274
|
+
if (typeof value !== "string") return false;
|
|
275
|
+
try {
|
|
276
|
+
new URL(value);
|
|
277
|
+
return true;
|
|
278
|
+
} catch {
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
var AVATAR_URL_MAX_LENGTH = 2048;
|
|
283
|
+
function validateAvatarUrl(value) {
|
|
284
|
+
if (value === void 0 || value === null) return null;
|
|
285
|
+
if (typeof value !== "string") {
|
|
286
|
+
return "avatarUrl \u5FC5\u987B\u662F\u5B57\u7B26\u4E32";
|
|
287
|
+
}
|
|
288
|
+
if (/^data:/i.test(value)) {
|
|
289
|
+
return "\u5934\u50CF\u4E0D\u652F\u6301\u4F20\u5165 data: URI\uFF0C\u8BF7\u6539\u4E3A\u516C\u7F51\u53EF\u8BBF\u95EE\u7684 https:// \u56FE\u7247 URL";
|
|
290
|
+
}
|
|
291
|
+
if (value.length > AVATAR_URL_MAX_LENGTH) {
|
|
292
|
+
return `\u5934\u50CF URL \u957F\u5EA6 ${value.length} \u5B57\u7B26\u8D85\u8FC7 ${AVATAR_URL_MAX_LENGTH} \u4E0A\u9650\uFF0C\u8BF7\u6539\u4E3A\u66F4\u77ED\u7684\u56FE\u7247 URL`;
|
|
293
|
+
}
|
|
294
|
+
if (!isValidUrl(value)) {
|
|
295
|
+
return "avatarUrl \u4E0D\u662F\u5408\u6CD5 URL";
|
|
296
|
+
}
|
|
297
|
+
return null;
|
|
298
|
+
}
|
|
299
|
+
function normalizeVapidSubject(email) {
|
|
300
|
+
const trimmed = String(email || "").trim();
|
|
301
|
+
if (!trimmed) return "";
|
|
302
|
+
return /^mailto:/i.test(trimmed) || /^https?:/i.test(trimmed) ? trimmed : `mailto:${trimmed}`;
|
|
303
|
+
}
|
|
304
|
+
var REASONING_TAG_RE = /<(think|thinking|thought)>([\s\S]*?)<\/\1>/i;
|
|
305
|
+
var REASONING_TAG_RE_G = /<(think|thinking|thought)>[\s\S]*?<\/\1>/gi;
|
|
306
|
+
function readReasoningContent(llmResponse) {
|
|
307
|
+
if (!llmResponse || typeof llmResponse !== "object") return null;
|
|
308
|
+
const choices = (
|
|
309
|
+
/** @type {{ choices?: unknown }} */
|
|
310
|
+
llmResponse.choices
|
|
311
|
+
);
|
|
312
|
+
if (!Array.isArray(choices) || choices.length === 0) return null;
|
|
313
|
+
const message = (
|
|
314
|
+
/** @type {{ message?: { reasoning_content?: unknown, content?: unknown } }} */
|
|
315
|
+
choices[0]?.message
|
|
316
|
+
);
|
|
317
|
+
const raw = message?.reasoning_content;
|
|
318
|
+
if (typeof raw === "string") {
|
|
319
|
+
const trimmed = raw.trim();
|
|
320
|
+
if (trimmed.length > 0) return trimmed;
|
|
321
|
+
}
|
|
322
|
+
const content = message?.content;
|
|
323
|
+
if (typeof content === "string") {
|
|
324
|
+
const match = content.match(REASONING_TAG_RE);
|
|
325
|
+
if (match) {
|
|
326
|
+
const trimmed = match[2].trim();
|
|
327
|
+
if (trimmed.length > 0) return trimmed;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
function stripReasoningTags(content) {
|
|
333
|
+
if (typeof content !== "string" || !content.includes("<")) return content;
|
|
334
|
+
return content.replace(REASONING_TAG_RE_G, "").trim();
|
|
335
|
+
}
|
package/dist/index.d.cts
CHANGED
|
@@ -247,6 +247,56 @@ export function base64UrlToBytes(input: string): Uint8Array;
|
|
|
247
247
|
* @returns {Uint8Array}
|
|
248
248
|
*/
|
|
249
249
|
export function concatBytes(...chunks: (Uint8Array | ArrayBuffer | ArrayBufferView)[]): Uint8Array;
|
|
250
|
+
/**
|
|
251
|
+
* True when `value` parses as an absolute URL.
|
|
252
|
+
* @param {unknown} value
|
|
253
|
+
* @returns {boolean}
|
|
254
|
+
*/
|
|
255
|
+
export function isValidUrl(value: unknown): boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Validate the optional `avatarUrl` field. Rejects `data:` URIs (typically
|
|
258
|
+
* base64-encoded inline images) and anything longer than
|
|
259
|
+
* {@link AVATAR_URL_MAX_LENGTH} chars — both the dominant trigger for
|
|
260
|
+
* downstream 413 / Web Push 4 KB payload errors — plus anything that doesn't
|
|
261
|
+
* parse as a URL. Returns an error message string, or null when valid.
|
|
262
|
+
*
|
|
263
|
+
* Pure: callers decide how to act on a non-null result (amsg-server /
|
|
264
|
+
* amsg-instant / amsg-client soft-strip + console.warn; see standards §6.2).
|
|
265
|
+
*
|
|
266
|
+
* @param {unknown} value
|
|
267
|
+
* @returns {string | null}
|
|
268
|
+
*/
|
|
269
|
+
export function validateAvatarUrl(value: unknown): string | null;
|
|
270
|
+
/**
|
|
271
|
+
* Normalize a VAPID `sub` (subject) claim. Web Push (RFC 8292) accepts a
|
|
272
|
+
* `mailto:` address or an `http(s):` URL; a bare contact like
|
|
273
|
+
* `you@example.com` is prefixed with `mailto:`. An already-prefixed
|
|
274
|
+
* `mailto:` / `http(s):` value is returned untouched. Empty / blank → `''`.
|
|
275
|
+
*
|
|
276
|
+
* @param {unknown} email
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
export function normalizeVapidSubject(email: unknown): string;
|
|
280
|
+
/**
|
|
281
|
+
* Read `choices[0].message.reasoning_content` as a non-empty trimmed string,
|
|
282
|
+
* or null when absent / empty. Falls back to the first `<think>` span inside
|
|
283
|
+
* `message.content` when a provider inlines reasoning there. Many providers
|
|
284
|
+
* return an empty string instead of omitting the field — treated the same as
|
|
285
|
+
* missing so callers don't emit an empty ReasoningPush.
|
|
286
|
+
*
|
|
287
|
+
* @param {unknown} llmResponse
|
|
288
|
+
* @returns {string | null}
|
|
289
|
+
*/
|
|
290
|
+
export function readReasoningContent(llmResponse: unknown): string | null;
|
|
291
|
+
/**
|
|
292
|
+
* Drop any `<think>` / `<thinking>` / `<thought>` spans from a user-facing
|
|
293
|
+
* content string, so private chain-of-thought leaking through `message.content`
|
|
294
|
+
* does not also ship inside the ContentPush burst.
|
|
295
|
+
*
|
|
296
|
+
* @param {string} content
|
|
297
|
+
* @returns {string}
|
|
298
|
+
*/
|
|
299
|
+
export function stripReasoningTags(content: string): string;
|
|
250
300
|
/**
|
|
251
301
|
* @rei-standard/amsg-shared
|
|
252
302
|
*
|
|
@@ -316,6 +366,8 @@ export const PUSH_SOURCE: Readonly<{
|
|
|
316
366
|
INSTANT: "instant";
|
|
317
367
|
SCHEDULED: "scheduled";
|
|
318
368
|
}>;
|
|
369
|
+
/** Max accepted `avatarUrl` length, in characters. */
|
|
370
|
+
export const AVATAR_URL_MAX_LENGTH: 2048;
|
|
319
371
|
/**
|
|
320
372
|
* Fields present on every push, regardless of kind. Discriminator
|
|
321
373
|
* fields (`messageKind`) and kind-specific fields live on the kind
|
|
@@ -439,24 +491,24 @@ export type ContentPush = AmsgPushCommon & {
|
|
|
439
491
|
* out of the upstream response into its own push. Emitted **before**
|
|
440
492
|
* the matching {@link ContentPush} burst when present and non-empty.
|
|
441
493
|
*
|
|
442
|
-
* Reasoning carries two
|
|
443
|
-
*
|
|
444
|
-
*
|
|
494
|
+
* Reasoning carries two optional "multi-part" axes, both *omitted* when
|
|
495
|
+
* the part count is 1 so the wire stays byte-for-byte compatible with
|
|
496
|
+
* single-shot callers. The type reserves them for forward compatibility;
|
|
497
|
+
* current producers emit a single ReasoningPush and set neither — oversized
|
|
498
|
+
* reasoning rides the generic multipart transport, not a reasoning-only
|
|
499
|
+
* chunk format.
|
|
445
500
|
*
|
|
446
|
-
* - `messageIndex` / `totalMessages` —
|
|
447
|
-
*
|
|
448
|
-
* reasoning into multiple sentences for typing-bubble UX.
|
|
501
|
+
* - `messageIndex` / `totalMessages` — a 1-based part index when a producer
|
|
502
|
+
* splits reasoning into multiple sentences for typing-bubble UX.
|
|
449
503
|
*
|
|
450
|
-
* - `chunkIndex` / `totalChunks` —
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* bucket. See `chunkReasoningByUtf8Bytes` for the safe-edge
|
|
456
|
-
* splitter helper.
|
|
504
|
+
* - `chunkIndex` / `totalChunks` — transport-only slicing when a single
|
|
505
|
+
* segment exceeds the Web Push payload limit; SW would reassemble the
|
|
506
|
+
* original `reasoningContent` by sorting on `chunkIndex` within a
|
|
507
|
+
* `(sessionId, messageIndex)` bucket. See `chunkReasoningByUtf8Bytes`
|
|
508
|
+
* for the safe-edge splitter helper.
|
|
457
509
|
*
|
|
458
|
-
* Both axes can coexist on the same push when a sentence-split
|
|
459
|
-
*
|
|
510
|
+
* Both axes can coexist on the same push when a sentence-split segment is
|
|
511
|
+
* itself oversized.
|
|
460
512
|
*/
|
|
461
513
|
export type ReasoningPush = AmsgPushCommon & {
|
|
462
514
|
messageKind: "reasoning";
|
package/dist/index.d.ts
CHANGED
|
@@ -247,6 +247,56 @@ export function base64UrlToBytes(input: string): Uint8Array;
|
|
|
247
247
|
* @returns {Uint8Array}
|
|
248
248
|
*/
|
|
249
249
|
export function concatBytes(...chunks: (Uint8Array | ArrayBuffer | ArrayBufferView)[]): Uint8Array;
|
|
250
|
+
/**
|
|
251
|
+
* True when `value` parses as an absolute URL.
|
|
252
|
+
* @param {unknown} value
|
|
253
|
+
* @returns {boolean}
|
|
254
|
+
*/
|
|
255
|
+
export function isValidUrl(value: unknown): boolean;
|
|
256
|
+
/**
|
|
257
|
+
* Validate the optional `avatarUrl` field. Rejects `data:` URIs (typically
|
|
258
|
+
* base64-encoded inline images) and anything longer than
|
|
259
|
+
* {@link AVATAR_URL_MAX_LENGTH} chars — both the dominant trigger for
|
|
260
|
+
* downstream 413 / Web Push 4 KB payload errors — plus anything that doesn't
|
|
261
|
+
* parse as a URL. Returns an error message string, or null when valid.
|
|
262
|
+
*
|
|
263
|
+
* Pure: callers decide how to act on a non-null result (amsg-server /
|
|
264
|
+
* amsg-instant / amsg-client soft-strip + console.warn; see standards §6.2).
|
|
265
|
+
*
|
|
266
|
+
* @param {unknown} value
|
|
267
|
+
* @returns {string | null}
|
|
268
|
+
*/
|
|
269
|
+
export function validateAvatarUrl(value: unknown): string | null;
|
|
270
|
+
/**
|
|
271
|
+
* Normalize a VAPID `sub` (subject) claim. Web Push (RFC 8292) accepts a
|
|
272
|
+
* `mailto:` address or an `http(s):` URL; a bare contact like
|
|
273
|
+
* `you@example.com` is prefixed with `mailto:`. An already-prefixed
|
|
274
|
+
* `mailto:` / `http(s):` value is returned untouched. Empty / blank → `''`.
|
|
275
|
+
*
|
|
276
|
+
* @param {unknown} email
|
|
277
|
+
* @returns {string}
|
|
278
|
+
*/
|
|
279
|
+
export function normalizeVapidSubject(email: unknown): string;
|
|
280
|
+
/**
|
|
281
|
+
* Read `choices[0].message.reasoning_content` as a non-empty trimmed string,
|
|
282
|
+
* or null when absent / empty. Falls back to the first `<think>` span inside
|
|
283
|
+
* `message.content` when a provider inlines reasoning there. Many providers
|
|
284
|
+
* return an empty string instead of omitting the field — treated the same as
|
|
285
|
+
* missing so callers don't emit an empty ReasoningPush.
|
|
286
|
+
*
|
|
287
|
+
* @param {unknown} llmResponse
|
|
288
|
+
* @returns {string | null}
|
|
289
|
+
*/
|
|
290
|
+
export function readReasoningContent(llmResponse: unknown): string | null;
|
|
291
|
+
/**
|
|
292
|
+
* Drop any `<think>` / `<thinking>` / `<thought>` spans from a user-facing
|
|
293
|
+
* content string, so private chain-of-thought leaking through `message.content`
|
|
294
|
+
* does not also ship inside the ContentPush burst.
|
|
295
|
+
*
|
|
296
|
+
* @param {string} content
|
|
297
|
+
* @returns {string}
|
|
298
|
+
*/
|
|
299
|
+
export function stripReasoningTags(content: string): string;
|
|
250
300
|
/**
|
|
251
301
|
* @rei-standard/amsg-shared
|
|
252
302
|
*
|
|
@@ -316,6 +366,8 @@ export const PUSH_SOURCE: Readonly<{
|
|
|
316
366
|
INSTANT: "instant";
|
|
317
367
|
SCHEDULED: "scheduled";
|
|
318
368
|
}>;
|
|
369
|
+
/** Max accepted `avatarUrl` length, in characters. */
|
|
370
|
+
export const AVATAR_URL_MAX_LENGTH: 2048;
|
|
319
371
|
/**
|
|
320
372
|
* Fields present on every push, regardless of kind. Discriminator
|
|
321
373
|
* fields (`messageKind`) and kind-specific fields live on the kind
|
|
@@ -439,24 +491,24 @@ export type ContentPush = AmsgPushCommon & {
|
|
|
439
491
|
* out of the upstream response into its own push. Emitted **before**
|
|
440
492
|
* the matching {@link ContentPush} burst when present and non-empty.
|
|
441
493
|
*
|
|
442
|
-
* Reasoning carries two
|
|
443
|
-
*
|
|
444
|
-
*
|
|
494
|
+
* Reasoning carries two optional "multi-part" axes, both *omitted* when
|
|
495
|
+
* the part count is 1 so the wire stays byte-for-byte compatible with
|
|
496
|
+
* single-shot callers. The type reserves them for forward compatibility;
|
|
497
|
+
* current producers emit a single ReasoningPush and set neither — oversized
|
|
498
|
+
* reasoning rides the generic multipart transport, not a reasoning-only
|
|
499
|
+
* chunk format.
|
|
445
500
|
*
|
|
446
|
-
* - `messageIndex` / `totalMessages` —
|
|
447
|
-
*
|
|
448
|
-
* reasoning into multiple sentences for typing-bubble UX.
|
|
501
|
+
* - `messageIndex` / `totalMessages` — a 1-based part index when a producer
|
|
502
|
+
* splits reasoning into multiple sentences for typing-bubble UX.
|
|
449
503
|
*
|
|
450
|
-
* - `chunkIndex` / `totalChunks` —
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
* bucket. See `chunkReasoningByUtf8Bytes` for the safe-edge
|
|
456
|
-
* splitter helper.
|
|
504
|
+
* - `chunkIndex` / `totalChunks` — transport-only slicing when a single
|
|
505
|
+
* segment exceeds the Web Push payload limit; SW would reassemble the
|
|
506
|
+
* original `reasoningContent` by sorting on `chunkIndex` within a
|
|
507
|
+
* `(sessionId, messageIndex)` bucket. See `chunkReasoningByUtf8Bytes`
|
|
508
|
+
* for the safe-edge splitter helper.
|
|
457
509
|
*
|
|
458
|
-
* Both axes can coexist on the same push when a sentence-split
|
|
459
|
-
*
|
|
510
|
+
* Both axes can coexist on the same push when a sentence-split segment is
|
|
511
|
+
* itself oversized.
|
|
460
512
|
*/
|
|
461
513
|
export type ReasoningPush = AmsgPushCommon & {
|
|
462
514
|
messageKind: "reasoning";
|
package/dist/index.mjs
CHANGED
|
@@ -227,7 +227,71 @@ function concatBytes(...chunks) {
|
|
|
227
227
|
}
|
|
228
228
|
return out;
|
|
229
229
|
}
|
|
230
|
+
function isValidUrl(value) {
|
|
231
|
+
if (typeof value !== "string") return false;
|
|
232
|
+
try {
|
|
233
|
+
new URL(value);
|
|
234
|
+
return true;
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
var AVATAR_URL_MAX_LENGTH = 2048;
|
|
240
|
+
function validateAvatarUrl(value) {
|
|
241
|
+
if (value === void 0 || value === null) return null;
|
|
242
|
+
if (typeof value !== "string") {
|
|
243
|
+
return "avatarUrl \u5FC5\u987B\u662F\u5B57\u7B26\u4E32";
|
|
244
|
+
}
|
|
245
|
+
if (/^data:/i.test(value)) {
|
|
246
|
+
return "\u5934\u50CF\u4E0D\u652F\u6301\u4F20\u5165 data: URI\uFF0C\u8BF7\u6539\u4E3A\u516C\u7F51\u53EF\u8BBF\u95EE\u7684 https:// \u56FE\u7247 URL";
|
|
247
|
+
}
|
|
248
|
+
if (value.length > AVATAR_URL_MAX_LENGTH) {
|
|
249
|
+
return `\u5934\u50CF URL \u957F\u5EA6 ${value.length} \u5B57\u7B26\u8D85\u8FC7 ${AVATAR_URL_MAX_LENGTH} \u4E0A\u9650\uFF0C\u8BF7\u6539\u4E3A\u66F4\u77ED\u7684\u56FE\u7247 URL`;
|
|
250
|
+
}
|
|
251
|
+
if (!isValidUrl(value)) {
|
|
252
|
+
return "avatarUrl \u4E0D\u662F\u5408\u6CD5 URL";
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
function normalizeVapidSubject(email) {
|
|
257
|
+
const trimmed = String(email || "").trim();
|
|
258
|
+
if (!trimmed) return "";
|
|
259
|
+
return /^mailto:/i.test(trimmed) || /^https?:/i.test(trimmed) ? trimmed : `mailto:${trimmed}`;
|
|
260
|
+
}
|
|
261
|
+
var REASONING_TAG_RE = /<(think|thinking|thought)>([\s\S]*?)<\/\1>/i;
|
|
262
|
+
var REASONING_TAG_RE_G = /<(think|thinking|thought)>[\s\S]*?<\/\1>/gi;
|
|
263
|
+
function readReasoningContent(llmResponse) {
|
|
264
|
+
if (!llmResponse || typeof llmResponse !== "object") return null;
|
|
265
|
+
const choices = (
|
|
266
|
+
/** @type {{ choices?: unknown }} */
|
|
267
|
+
llmResponse.choices
|
|
268
|
+
);
|
|
269
|
+
if (!Array.isArray(choices) || choices.length === 0) return null;
|
|
270
|
+
const message = (
|
|
271
|
+
/** @type {{ message?: { reasoning_content?: unknown, content?: unknown } }} */
|
|
272
|
+
choices[0]?.message
|
|
273
|
+
);
|
|
274
|
+
const raw = message?.reasoning_content;
|
|
275
|
+
if (typeof raw === "string") {
|
|
276
|
+
const trimmed = raw.trim();
|
|
277
|
+
if (trimmed.length > 0) return trimmed;
|
|
278
|
+
}
|
|
279
|
+
const content = message?.content;
|
|
280
|
+
if (typeof content === "string") {
|
|
281
|
+
const match = content.match(REASONING_TAG_RE);
|
|
282
|
+
if (match) {
|
|
283
|
+
const trimmed = match[2].trim();
|
|
284
|
+
if (trimmed.length > 0) return trimmed;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
function stripReasoningTags(content) {
|
|
290
|
+
if (typeof content !== "string" || !content.includes("<")) return content;
|
|
291
|
+
return content.replace(REASONING_TAG_RE_G, "").trim();
|
|
292
|
+
}
|
|
230
293
|
export {
|
|
294
|
+
AVATAR_URL_MAX_LENGTH,
|
|
231
295
|
MESSAGE_KIND,
|
|
232
296
|
MESSAGE_TYPE,
|
|
233
297
|
PUSH_SOURCE,
|
|
@@ -242,5 +306,10 @@ export {
|
|
|
242
306
|
isErrorPush,
|
|
243
307
|
isReasoningPush,
|
|
244
308
|
isToolRequestPush,
|
|
245
|
-
|
|
309
|
+
isValidUrl,
|
|
310
|
+
normalizeVapidSubject,
|
|
311
|
+
readReasoningContent,
|
|
312
|
+
stripReasoningTags,
|
|
313
|
+
toUint8,
|
|
314
|
+
validateAvatarUrl
|
|
246
315
|
};
|
package/package.json
CHANGED