
























@@ -42,8 +42,51 @@ import {
4242import { hasNonzeroUsage, normalizeUsage, type UsageLike } from "./usage.js";
43434444const FINAL_TAG_SCAN_RE = /<\s*(\/?)\s*final\s*>/gi;
45+const STREAM_STRIPPED_BLOCK_TAG_NAMES = [
46+"final",
47+"think",
48+"thinking",
49+"thought",
50+"antthinking",
51+"antml:think",
52+"antml:thinking",
53+"antml:thought",
54+] as const;
4555const log = createSubsystemLogger("agent/embedded");
465657+function isPotentialTrailingBlockTagFragment(fragment: string): boolean {
58+if (!fragment.startsWith("<") || fragment.includes(">")) {
59+return false;
60+}
61+const normalized = fragment.toLowerCase().replace(/\s+/g, "");
62+if (!normalized.startsWith("<")) {
63+return false;
64+}
65+const candidate = normalized.slice(1).replace(/^\//, "");
66+if (!candidate) {
67+return true;
68+}
69+return STREAM_STRIPPED_BLOCK_TAG_NAMES.some((name) => name.startsWith(candidate));
70+}
71+72+function splitTrailingBlockTagFragment(
73+text: string,
74+isInsideCodeSpan: (index: number) => boolean,
75+): { text: string; pendingTagFragment?: string } {
76+const fragmentStart = text.lastIndexOf("<");
77+if (fragmentStart === -1 || isInsideCodeSpan(fragmentStart)) {
78+return { text };
79+}
80+const fragment = text.slice(fragmentStart);
81+if (!isPotentialTrailingBlockTagFragment(fragment)) {
82+return { text };
83+}
84+return {
85+text: text.slice(0, fragmentStart),
86+pendingTagFragment: fragment,
87+};
88+}
89+4790function collectPendingMediaFromInternalEvents(
4891events: SubscribeEmbeddedPiSessionParams["internalEvents"],
4992): string[] {
@@ -213,9 +256,11 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
213256state.blockState.thinking = false;
214257state.blockState.final = false;
215258state.blockState.inlineCode = createInlineCodeState();
259+state.blockState.pendingTagFragment = undefined;
216260state.partialBlockState.thinking = false;
217261state.partialBlockState.final = false;
218262state.partialBlockState.inlineCode = createInlineCodeState();
263+state.partialBlockState.pendingTagFragment = undefined;
219264state.lastStreamedAssistant = undefined;
220265state.lastStreamedAssistantCleaned = undefined;
221266state.emittedAssistantUpdate = false;
@@ -521,20 +566,36 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
521566522567const stripBlockTags = (
523568text: string,
524-state: { thinking: boolean; final: boolean; inlineCode?: InlineCodeState },
569+state: {
570+thinking: boolean;
571+final: boolean;
572+inlineCode?: InlineCodeState;
573+pendingTagFragment?: string;
574+},
575+options?: { final?: boolean },
525576): string => {
526-if (!text) {
577+const input = `${state.pendingTagFragment ?? ""}${text}`;
578+state.pendingTagFragment = undefined;
579+if (!input) {
527580return text;
528581}
529582530583const inlineStateStart = state.inlineCode ?? createInlineCodeState();
531-const codeSpans = buildCodeSpanIndex(text, inlineStateStart);
584+const initialCodeSpans = buildCodeSpanIndex(input, inlineStateStart);
585+const { text: scanText, pendingTagFragment } = options?.final
586+ ? { text: input, pendingTagFragment: undefined }
587+ : splitTrailingBlockTagFragment(input, initialCodeSpans.isInside);
588+state.pendingTagFragment = pendingTagFragment;
589+if (!scanText) {
590+return "";
591+}
592+const codeSpans = buildCodeSpanIndex(scanText, inlineStateStart);
532593533594let processed = "";
534595THINKING_TAG_SCAN_RE.lastIndex = 0;
535596let lastIndex = 0;
536597let inThinking = state.thinking;
537-for (const match of text.matchAll(THINKING_TAG_SCAN_RE)) {
598+for (const match of scanText.matchAll(THINKING_TAG_SCAN_RE)) {
538599const idx = match.index ?? 0;
539600if (codeSpans.isInside(idx)) {
540601continue;
@@ -543,8 +604,8 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
543604if (!inThinking) {
544605if (isClose) {
545606const afterIndex = idx + match[0].length;
546-const before = text.slice(lastIndex, idx);
547-const after = text.slice(afterIndex);
607+const before = scanText.slice(lastIndex, idx);
608+const after = scanText.slice(afterIndex);
548609if (hasOrphanReasoningCloseBoundary({ before, after })) {
549610processed = "";
550611} else {
@@ -553,13 +614,13 @@ export function subscribeEmbeddedPiSession(params: SubscribeEmbeddedPiSessionPar
553614lastIndex = afterIndex;
554615continue;
555616}
556-processed += text.slice(lastIndex, idx);
617+processed += scanText.slice(lastIndex, idx);
557618}
558619inThinking = !isClose;
559620lastIndex = idx + match[0].length;
560621}
561622if (!inThinking) {
562-processed += text.slice(lastIndex);
623+processed += scanText.slice(lastIndex);
563624}
564625state.thinking = inThinking;
565626此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。