Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/rei-standard-amsg/instant/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog — @rei-standard/amsg-instant

## 0.8.2 — readReasoningContent fallback

- **Enhancement**: `readReasoningContent` 添加 fallback 支持。当原生 `reasoning_content` 字段缺失时,会 fallback 检查 `message.content` 是否包含 `<think>...</think>`、`<thinking>...</thinking>` 或 `<thought>...</thought>` 并提取,提供对更多模型(例如 DeepSeek-R1-Distill)的原生兼容。

## 0.8.1 — segmentTextWithProtectedBlocks utility

- **New**: 增加包级独立 utility `segmentTextWithProtectedBlocks`。该工具用于帮助 caller 将带有“不可拆片段”(如 Markdown 代码块、特定标记)的文本切分为 `PushTextSegment` 数组。纯正则匹配保护机制,不引入业务耦合,并支持自定义 preview 与 metadata,帮助更安全、方便地构建 hook 的 `pushPayloads` 返回值。
Expand Down
2 changes: 1 addition & 1 deletion packages/rei-standard-amsg/instant/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 内含 `<thinking>` 等标签)。`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。 |

### 鉴权策略
Expand Down
2 changes: 1 addition & 1 deletion packages/rei-standard-amsg/instant/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
21 changes: 17 additions & 4 deletions packages/rei-standard-amsg/instant/src/message-processor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the LLM response is cut off (e.g., due to reaching max_tokens or budget limits), the closing tag </think> (or </thinking>, </thought>) might be missing from the end of the content. In such cases, the current regex will fail to match, and no reasoning content will be extracted.\n\nTo make the extraction more robust, we can update the regex to allow matching until the end of the string ($) if the closing tag is absent.

Suggested change
const match = content.match(/<(think|thinking|thought)>([\s\S]*?)<\/\1>/i);
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;
}

/**
Expand Down
4 changes: 4 additions & 0 deletions packages/rei-standard-amsg/server/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog — @rei-standard/amsg-server

## 2.4.1 — readReasoningContent fallback

- **Enhancement**: `readReasoningContent` 添加 fallback 支持。当原生 `reasoning_content` 字段缺失时,会 fallback 检查 `message.content` 是否包含 `<think>...</think>`、`<thinking>...</thinking>` 或 `<thought>...</thought>` 并提取,提供对更多模型(例如 DeepSeek-R1-Distill)的原生兼容。

## 2.4.0 — Dependency bump

- 依赖更新:同步升级 `@rei-standard/amsg-shared` 至稳定版 `0.1.0`。
Expand Down
2 changes: 1 addition & 1 deletion packages/rei-standard-amsg/server/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the LLM response is cut off (e.g., due to reaching max_tokens or budget limits), the closing tag </think> (or </thinking>, </thought>) might be missing from the end of the content. In such cases, the current regex will fail to match, and no reasoning content will be extracted.\n\nTo make the extraction more robust, we can update the regex to allow matching until the end of the string ($) if the closing tag is absent.

Suggested change
const match = content.match(/<(think|thinking|thought)>([\s\S]*?)<\/\1>/i);
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;
}

/**
Expand Down
Loading