From bd2217932a02ae5c29c24c4236292c863d168c52 Mon Sep 17 00:00:00 2001 From: Tosd0 <65720409+Tosd0@users.noreply.github.com> Date: Thu, 28 May 2026 23:20:01 +0800 Subject: [PATCH] feat(amsg): add fallback for readReasoningContent --- package-lock.json | 6 +++--- .../rei-standard-amsg/instant/CHANGELOG.md | 4 ++++ packages/rei-standard-amsg/instant/README.md | 2 +- .../rei-standard-amsg/instant/package.json | 2 +- .../instant/src/message-processor.js | 21 +++++++++++++++---- .../rei-standard-amsg/server/CHANGELOG.md | 4 ++++ .../rei-standard-amsg/server/package.json | 2 +- .../src/server/lib/message-processor.js | 21 +++++++++++++++---- 8 files changed, 48 insertions(+), 14 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3bb03cc..690b80c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "push-schema", + "name": "ReiStandard", "lockfileVersion": 3, "requires": true, "packages": { @@ -1865,7 +1865,7 @@ }, "packages/rei-standard-amsg/instant": { "name": "@rei-standard/amsg-instant", - "version": "0.8.1", + "version": "0.8.2", "license": "MIT", "dependencies": { "@rei-standard/amsg-shared": "0.1.0" @@ -1880,7 +1880,7 @@ }, "packages/rei-standard-amsg/server": { "name": "@rei-standard/amsg-server", - "version": "2.4.0", + "version": "2.4.1", "license": "MIT", "dependencies": { "@netlify/blobs": "^8.1.0", diff --git a/packages/rei-standard-amsg/instant/CHANGELOG.md b/packages/rei-standard-amsg/instant/CHANGELOG.md index 17e4ef6..6284340 100644 --- a/packages/rei-standard-amsg/instant/CHANGELOG.md +++ b/packages/rei-standard-amsg/instant/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog — @rei-standard/amsg-instant +## 0.8.2 — readReasoningContent fallback + +- **Enhancement**: `readReasoningContent` 添加 fallback 支持。当原生 `reasoning_content` 字段缺失时,会 fallback 检查 `message.content` 是否包含 `...`、`...` 或 `...` 并提取,提供对更多模型(例如 DeepSeek-R1-Distill)的原生兼容。 + ## 0.8.1 — segmentTextWithProtectedBlocks utility - **New**: 增加包级独立 utility `segmentTextWithProtectedBlocks`。该工具用于帮助 caller 将带有“不可拆片段”(如 Markdown 代码块、特定标记)的文本切分为 `PushTextSegment` 数组。纯正则匹配保护机制,不引入业务耦合,并支持自定义 preview 与 metadata,帮助更安全、方便地构建 hook 的 `pushPayloads` 返回值。 diff --git a/packages/rei-standard-amsg/instant/README.md b/packages/rei-standard-amsg/instant/README.md index 647790b..76a5f69 100644 --- a/packages/rei-standard-amsg/instant/README.md +++ b/packages/rei-standard-amsg/instant/README.md @@ -40,7 +40,7 @@ npm install @rei-standard/amsg-instant | `blobStore` | object | ❌ | **0.7.0+**:可选 blob 后端。push payload UTF-8 字节超过 `maxInlineBytes`(默认 2600)时自动把 body 写进 store、改推 200 B envelope。见 [BlobStore](#blobstore070) | | `multipart` | object | ❌ | **0.8.0+**:通用 multipart transport。超出 inline、且没配 BlobStore 时,任意 JSON-safe payload 都可拆成 `_multipart` 分片。默认 `enabled:true`、`maxChunkBytes:1800`、`ttlMs:60000`、`maxChunks:128`、`maxTotalBytes:256000`。见 [Generic multipart transport](#generic-multipart-transport080)。 | | `maxLoopIterations` | number | ❌ | **0.7.0+**:单次 worker 调用内 `decision:'continue'` 的硬上限,默认 10。仅防本进程内 hook 反复 continue 失控;跨请求的 `/continue` 洪水攻击由上游 auth/rate-limit 处理 | -| `autoEmitReasoning` | boolean | ❌ | **0.8.0+**:默认 `true`。`true` 时框架在调 hook 前自动 emit `ReasoningPush`(如果 LLM 响应带非空 `reasoning_content`)。`false` 把 reasoning emit 完全交给 hook 自己负责(hook 可读 `ctx.llmResponse.choices[0].message.reasoning_content` 并用 `buildReasoningPush` + 自己 dispatch)。legacy 路径忽略此项始终自动 emit。 | +| `autoEmitReasoning` | boolean | ❌ | **0.8.0+**:默认 `true`。`true` 时框架在调 hook 前自动 emit `ReasoningPush`(如果 LLM 响应带非空 `reasoning_content`,或 `content` 内含 `` 等标签)。`false` 把 reasoning emit 完全交给 hook 自己负责(hook 可读 `ctx.llmResponse.choices[0].message.reasoning_content` 并用 `buildReasoningPush` + 自己 dispatch)。legacy 路径忽略此项始终自动 emit。 | | `reasoningChunkBytes` | number \| null | ❌ | **Deprecated in 0.8.0**:旧 reasoning 专用字节切配置。保留为 `multipart.maxChunkBytes` 的兼容别名;`null` 仅在未显式配置 `multipart` 时禁用 generic multipart。不会再产生 `chunkIndex` / `totalChunks` reasoning wire fields。 | ### 鉴权策略 diff --git a/packages/rei-standard-amsg/instant/package.json b/packages/rei-standard-amsg/instant/package.json index ffc9498..21ad9c8 100644 --- a/packages/rei-standard-amsg/instant/package.json +++ b/packages/rei-standard-amsg/instant/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-instant", - "version": "0.8.1", + "version": "0.8.2", "description": "ReiStandard Active Messaging — agentic-loop framework for instant push. Pluggable per-turn hook + optional blob envelope for oversize payloads. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content. Pure Web Crypto. Deployable to Cloudflare Workers / Vercel Edge / Netlify / Node with no flags.", "repository": { "type": "git", diff --git a/packages/rei-standard-amsg/instant/src/message-processor.js b/packages/rei-standard-amsg/instant/src/message-processor.js index 453a281..42876a0 100644 --- a/packages/rei-standard-amsg/instant/src/message-processor.js +++ b/packages/rei-standard-amsg/instant/src/message-processor.js @@ -277,11 +277,24 @@ function readReasoningContent(llmResponse) { if (!llmResponse || typeof llmResponse !== 'object') return null; const choices = /** @type {{ choices?: unknown }} */ (llmResponse).choices; if (!Array.isArray(choices) || choices.length === 0) return null; - const message = /** @type {{ message?: { reasoning_content?: unknown } }} */ (choices[0])?.message; + const message = /** @type {{ message?: { reasoning_content?: unknown, content?: unknown } }} */ (choices[0])?.message; + const raw = message?.reasoning_content; - if (typeof raw !== 'string') return null; - const trimmed = raw.trim(); - return trimmed.length > 0 ? trimmed : null; + if (typeof raw === 'string') { + const trimmed = raw.trim(); + if (trimmed.length > 0) return trimmed; + } + + const content = message?.content; + if (typeof content === 'string') { + const match = content.match(/<(think|thinking|thought)>([\s\S]*?)<\/\1>/i); + if (match) { + const trimmed = match[2].trim(); + if (trimmed.length > 0) return trimmed; + } + } + + return null; } /** diff --git a/packages/rei-standard-amsg/server/CHANGELOG.md b/packages/rei-standard-amsg/server/CHANGELOG.md index 04c2261..bc7650e 100644 --- a/packages/rei-standard-amsg/server/CHANGELOG.md +++ b/packages/rei-standard-amsg/server/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog — @rei-standard/amsg-server +## 2.4.1 — readReasoningContent fallback + +- **Enhancement**: `readReasoningContent` 添加 fallback 支持。当原生 `reasoning_content` 字段缺失时,会 fallback 检查 `message.content` 是否包含 `...`、`...` 或 `...` 并提取,提供对更多模型(例如 DeepSeek-R1-Distill)的原生兼容。 + ## 2.4.0 — Dependency bump - 依赖更新:同步升级 `@rei-standard/amsg-shared` 至稳定版 `0.1.0`。 diff --git a/packages/rei-standard-amsg/server/package.json b/packages/rei-standard-amsg/server/package.json index 167fa44..ccdfeb7 100644 --- a/packages/rei-standard-amsg/server/package.json +++ b/packages/rei-standard-amsg/server/package.json @@ -1,6 +1,6 @@ { "name": "@rei-standard/amsg-server", - "version": "2.4.0", + "version": "2.4.1", "description": "ReiStandard Active Messaging server SDK with pluggable database adapters. Three-axis push schema (messageKind / messageType / messageSubtype) from @rei-standard/amsg-shared. Auto-emits ReasoningPush when the LLM response carries reasoning_content.", "repository": { "type": "git", diff --git a/packages/rei-standard-amsg/server/src/server/lib/message-processor.js b/packages/rei-standard-amsg/server/src/server/lib/message-processor.js index 754d9c8..02c1402 100644 --- a/packages/rei-standard-amsg/server/src/server/lib/message-processor.js +++ b/packages/rei-standard-amsg/server/src/server/lib/message-processor.js @@ -88,11 +88,24 @@ function readReasoningContent(llmResponse) { if (!llmResponse || typeof llmResponse !== 'object') return null; const choices = /** @type {{ choices?: unknown }} */ (llmResponse).choices; if (!Array.isArray(choices) || choices.length === 0) return null; - const message = /** @type {{ message?: { reasoning_content?: unknown } }} */ (choices[0])?.message; + const message = /** @type {{ message?: { reasoning_content?: unknown, content?: unknown } }} */ (choices[0])?.message; + const raw = message?.reasoning_content; - if (typeof raw !== 'string') return null; - const trimmed = raw.trim(); - return trimmed.length > 0 ? trimmed : null; + if (typeof raw === 'string') { + const trimmed = raw.trim(); + if (trimmed.length > 0) return trimmed; + } + + const content = message?.content; + if (typeof content === 'string') { + const match = content.match(/<(think|thinking|thought)>([\s\S]*?)<\/\1>/i); + if (match) { + const trimmed = match[2].trim(); + if (trimmed.length > 0) return trimmed; + } + } + + return null; } /**