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;
}
/**