mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 00:40:45 +08:00
fix(chat): persist/split <thinking>/<thought> blocks like stream UI
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7ea02e66ca
commit
46ebfeb54f
3 changed files with 36 additions and 3 deletions
|
|
@ -1547,10 +1547,12 @@ function hydrateMermaidIn(root) {
|
|||
|
||||
const RE_REDACTED_THINKING = new RegExp("<redacted_thinking>\\s*([\\s\\S]*?)\\s*</redacted_thinking>", "i");
|
||||
const RE_THINK_TAG = new RegExp("<think\\s*>\\s*([\\s\\S]*?)\\s*</think\\s*>", "i");
|
||||
const RE_THINKING_TAG = new RegExp("<thinking\\s*>\\s*([\\s\\S]*?)\\s*</thinking\\s*>", "i");
|
||||
const RE_THOUGHT_TAG = new RegExp("<thought\\s*>\\s*([\\s\\S]*?)\\s*</thought\\s*>", "i");
|
||||
|
||||
function _findEarliestReasoningBlock(remaining) {
|
||||
let best = null;
|
||||
for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG]) {
|
||||
for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG, RE_THINKING_TAG, RE_THOUGHT_TAG]) {
|
||||
const m = re.exec(remaining);
|
||||
re.lastIndex = 0;
|
||||
if (!m) continue;
|
||||
|
|
@ -1657,7 +1659,7 @@ function extractThinkingFromText(text) {
|
|||
const raw = String(text || "");
|
||||
if (!raw) return "";
|
||||
const blocks = [];
|
||||
for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG]) {
|
||||
for (const re of [RE_REDACTED_THINKING, RE_THINK_TAG, RE_THINKING_TAG, RE_THOUGHT_TAG]) {
|
||||
const rr = new RegExp(re.source, "gi");
|
||||
for (const m of raw.matchAll(rr)) {
|
||||
const inner = String(m[1] || "").trim();
|
||||
|
|
|
|||
|
|
@ -35,7 +35,10 @@ _DIRECT_LOOP_OC_STAGE: dict[str, str] = {
|
|||
"tool_result_context_guard": "tool_context_guard",
|
||||
"tool_pairing_guard": "tool_pairing_guard",
|
||||
}
|
||||
_THINK_BLOCK_RE = re.compile(r"<(think|redacted_thinking)>\s*(.*?)\s*</\1>\s*", flags=re.IGNORECASE | re.DOTALL)
|
||||
_THINK_BLOCK_RE = re.compile(
|
||||
r"<\s*(?:antml:)?(think|thinking|thought|redacted_thinking)\s*>\s*(.*?)\s*</\s*(?:antml:)?\1\s*>",
|
||||
flags=re.IGNORECASE | re.DOTALL,
|
||||
)
|
||||
_DSML_INVOKE_NAME_RE = re.compile(r"invoke\s+name\s*=\s*['\"]([^'\"\s>]+)['\"]", flags=re.IGNORECASE)
|
||||
_JSON_TOOL_NAME_RE = re.compile(r"['\"]name['\"]\s*:\s*['\"]([^'\"\s]{1,120})['\"]", flags=re.IGNORECASE)
|
||||
_TOOL_WIRE_CACHE_LOCK = threading.Lock()
|
||||
|
|
|
|||
28
tests/test_direct_loop_reasoning_split.py
Normal file
28
tests/test_direct_loop_reasoning_split.py
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from oclaw.runtime.direct_loop import _split_reasoning_and_body
|
||||
|
||||
|
||||
def test_split_reasoning_extracts_thinking_tags() -> None:
|
||||
chunks, body = _split_reasoning_and_body("<thinking>\ninner\n</thinking>\nvisible")
|
||||
assert chunks == ["inner"]
|
||||
assert body == "visible"
|
||||
|
||||
|
||||
def test_split_reasoning_extracts_thought_tags() -> None:
|
||||
chunks, body = _split_reasoning_and_body("<thought>x</thought>after")
|
||||
assert chunks == ["x"]
|
||||
assert body == "after"
|
||||
|
||||
|
||||
def test_split_reasoning_explicit_when_no_tags() -> None:
|
||||
chunks, body = _split_reasoning_and_body("body only", explicit_reasoning=" rc ")
|
||||
assert chunks == ["rc"]
|
||||
assert body == "body only"
|
||||
|
||||
|
||||
def test_split_reasoning_antml_optional_prefix() -> None:
|
||||
raw = "<antml:thinking>\nZ\n</antml:thinking>\nok"
|
||||
chunks, body = _split_reasoning_and_body(raw)
|
||||
assert chunks == ["Z"]
|
||||
assert body == "ok"
|
||||
Loading…
Add table
Add a link
Reference in a new issue