fix(coding-agent): preserved markerless thinking titles
- Limited markerless fallback cleanup to leading leaked-thinking envelopes so literal reasoning syntax in plain titles survives. - Added markerless regression coverage for think tags and thinking-fence titles. Fixes #5122
This commit is contained in:
@@ -35,6 +35,8 @@ const TITLE_MAX_TOKENS = 1024;
|
||||
/** Matches the title the model wraps in `<title>...</title>`. */
|
||||
const TITLE_MARKER_GLOBAL_RE = /<title>([\s\S]*?)<\/title>/gi;
|
||||
const TITLE_VISIBILITY_SENTINEL = "\uE000omp-title-visible\uE000";
|
||||
const LEADING_THINKING_TAG_RE = /^\s*<(think|thinking|reasoning)>\s*[\s\S]*?<\/\1>\s*/i;
|
||||
const LEADING_THINKING_FENCE_RE = /^\s*```(?:thinking|reasoning)\b[\s\S]*?```\s*/i;
|
||||
|
||||
function getTitleModel(registry: ModelRegistry, settings: Settings, currentModel?: Model<Api>): Model<Api> | undefined {
|
||||
const availableModels = registry.getAvailable();
|
||||
@@ -244,12 +246,12 @@ function extractGeneratedTitle(contentBlocks: AssistantMessage["content"]): stri
|
||||
}
|
||||
}
|
||||
// Stay lenient: prefer the first closed title marker in visible text, then
|
||||
// fall back to a plain sentence after stripping leaked thinking plus any
|
||||
// stray/unclosed title tag fragment (e.g. output truncated before closing).
|
||||
// fall back to a plain sentence after stripping only known leading leaked
|
||||
// thinking envelopes plus any stray/unclosed title tag fragment.
|
||||
const markedTitle = extractVisibleMarkedTitle(textTitle);
|
||||
const cleanedTextTitle =
|
||||
markedTitle ??
|
||||
stripLeakedThinkingMarkup(textTitle)
|
||||
stripLeadingLeakedThinkingMarkup(textTitle)
|
||||
.replace(/<\/?title>/gi, "")
|
||||
.trim();
|
||||
return unwrapJsonTitle(cleanedTextTitle);
|
||||
@@ -272,6 +274,16 @@ function isVisibleTitleMarker(text: string, markerIndex: number): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function stripLeadingLeakedThinkingMarkup(text: string): string {
|
||||
let current = text;
|
||||
while (true) {
|
||||
const withoutTag = current.replace(LEADING_THINKING_TAG_RE, "");
|
||||
const withoutFence = withoutTag.replace(LEADING_THINKING_FENCE_RE, "");
|
||||
if (withoutFence === current) return current;
|
||||
current = withoutFence;
|
||||
}
|
||||
}
|
||||
|
||||
function stripLeakedThinkingMarkup(text: string): string {
|
||||
const healer = new StreamMarkupHealing({ pattern: "thinking" });
|
||||
return healer.feed(text) + healer.flushPending();
|
||||
|
||||
@@ -377,6 +377,39 @@ describe("title generator", () => {
|
||||
expect(title).toBe("Fix login button on mobile");
|
||||
});
|
||||
|
||||
it("preserves a markerless title that mentions a <think> tag", async () => {
|
||||
const model = getModelFor("deepseek", "deepseek-v4-pro");
|
||||
vi.spyOn(ai, "completeSimple").mockResolvedValue({
|
||||
stopReason: "stop",
|
||||
content: [{ type: "text", text: "Fix <think> tag parsing" }],
|
||||
} as never);
|
||||
|
||||
const title = await generateSessionTitle(
|
||||
"fix title generation for <think> tag parsing",
|
||||
createRegistry(model),
|
||||
createSettings(model),
|
||||
);
|
||||
|
||||
expect(title).toBe("Fix <think> tag parsing");
|
||||
});
|
||||
|
||||
it("preserves a markerless title that mentions a ```thinking fence", async () => {
|
||||
const model = getModelFor("deepseek", "deepseek-v4-pro");
|
||||
vi.spyOn(ai, "completeSimple").mockResolvedValue({
|
||||
stopReason: "stop",
|
||||
content: [{ type: "text", text: "Fix ```thinking fence parsing" }],
|
||||
} as never);
|
||||
|
||||
const title = await generateSessionTitle(
|
||||
"fix title generation for a ```thinking fence",
|
||||
createRegistry(model),
|
||||
createSettings(model),
|
||||
);
|
||||
|
||||
expect(title).toContain("```thinking");
|
||||
expect(title).toContain("fence");
|
||||
});
|
||||
|
||||
it("strips an unclosed <title> tag from a truncated response", async () => {
|
||||
const model = getModelFor("deepseek", "deepseek-v4-pro");
|
||||
vi.spyOn(ai, "completeSimple").mockResolvedValue({
|
||||
|
||||
Reference in New Issue
Block a user