Skip to content
Open
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
13 changes: 10 additions & 3 deletions core/autocomplete/postprocessing/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,16 @@ export function postprocessCompletion({

if (llm.model.includes("qwen3")) {
// Qwen3 always starts from special thinking markers, and we don't want them to output these contents
// Remove all content from "
completion = completion.replace(/<think>.*?<\/think>/s, "");
completion = completion.replace(/<\/think>/, "");
// Remove all content within thinking tags. Use the configurable thinkTagName so custom
// provider formats (e.g. vLLM reasoning output tags) are also handled correctly.
const thinkTagName = llm.thinkTagName;
const thinkBlockRegex = new RegExp(
`<${thinkTagName}>.*?<\\/${thinkTagName}>`,
"s",
);
const thinkCloseTagRegex = new RegExp(`<\\/${thinkTagName}>`);
completion = completion.replace(thinkBlockRegex, "");
completion = completion.replace(thinkCloseTagRegex, "");

// Remove any number of newline characters at the beginning and end
completion = completion.replace(/^\n+|\n+$/g, "");
Expand Down
16 changes: 16 additions & 0 deletions core/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,13 @@ declare global {
apiType?: string;
region?: string;
projectId?: string;

/**
* The XML tag name used for thinking/reasoning output.
* Defaults to "think" (<think>...</think>).
* Configure this to match your provider's format (e.g. vLLM custom reasoning tags).
*/
thinkTagName: string;

// Embedding options
embeddingId: string;
Expand Down Expand Up @@ -572,6 +579,15 @@ declare global {

// IBM watsonx Options
deploymentId?: string;

/**
* The XML tag name used by the LLM provider for thinking/reasoning output.
* Different providers (e.g. vLLM, Ollama) may use different tag names.
* Defaults to "think", which produces <think>...</think> blocks.
* Set this to match your provider's reasoning output format.
* See: https://docs.vllm.ai/en/latest/features/reasoning_outputs.html
*/
thinkTagName?: string;

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.

P2: The configurable thinkTagName is documented as an XML tag name, but downstream code interpolates it directly into new RegExp() without escaping regex metacharacters. A user setting thinkTagName to something like reason[ing] or think.* would hit a runtime SyntaxError or match unintended content. The codebase already has escapeLiteralForRegex in core/util/regexValidator.ts; it should be applied when building the think-block regexes in core/util/index.ts and core/autocomplete/postprocessing/index.ts (or the value should be escaped when assigned in BaseLLM).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At core/config/types.ts, line 583:

<comment>The configurable `thinkTagName` is documented as an XML tag name, but downstream code interpolates it directly into `new RegExp()` without escaping regex metacharacters. A user setting `thinkTagName` to something like `reason[ing]` or `think.*` would hit a runtime `SyntaxError` or match unintended content. The codebase already has `escapeLiteralForRegex` in `core/util/regexValidator.ts`; it should be applied when building the think-block regexes in `core/util/index.ts` and `core/autocomplete/postprocessing/index.ts` (or the value should be escaped when assigned in `BaseLLM`).</comment>

<file context>
@@ -572,6 +572,15 @@ declare global {
+     * Set this to match your provider's reasoning output format.
+     * See: https://docs.vllm.ai/en/latest/features/reasoning_outputs.html
+     */
+    thinkTagName?: string;
   }
   
</file context>

}

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
Expand Down
8 changes: 8 additions & 0 deletions core/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,14 @@ export interface LLMOptions {

/** Tool overrides for this model */
toolOverrides?: ToolOverride[];

/**
* The XML tag name used by the LLM provider for thinking/reasoning output.
* Defaults to "think", which produces <think>...</think> blocks.
* Configure this to match your provider's reasoning output format.
* See: https://docs.vllm.ai/en/latest/features/reasoning_outputs.html
*/
thinkTagName?: string;
}

type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<
Expand Down
10 changes: 10 additions & 0 deletions core/llm/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ export abstract class BaseLLM implements ILLM {
// For IBM watsonx
deploymentId?: string;

/**
* The XML tag name used for thinking/reasoning output.
* Defaults to "think" (<think>...</think>).
* Override via config to match your provider (e.g. vLLM custom reasoning tags).
*/
thinkTagName: string;

// Embedding options
embeddingId: string;
maxEmbeddingChunkSize: number;
Expand Down Expand Up @@ -272,6 +279,9 @@ export abstract class BaseLLM implements ILLM {
// watsonx deploymentId
this.deploymentId = options.deploymentId;

// Thinking/reasoning output tag name (configurable for providers like vLLM)
this.thinkTagName = options.thinkTagName ?? "think";

if (this.apiBase && !this.apiBase.endsWith("/")) {
this.apiBase = `${this.apiBase}/`;
}
Expand Down
6 changes: 4 additions & 2 deletions core/llm/llms/Ollama.ts
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,8 @@ class Ollama extends BaseLLM implements ModelInstaller {
signal,
});
let isThinking: boolean = false;
const thinkOpenTag = `<${this.thinkTagName}>`;
const thinkCloseTag = `</${this.thinkTagName}>`;

function convertChatMessage(res: OllamaChatResponse): ChatMessage[] {
if ("error" in res) {
Expand All @@ -544,7 +546,7 @@ class Ollama extends BaseLLM implements ModelInstaller {
if ("type" in res) {
const { content } = res;

if (content === "<think>") {
if (content === thinkOpenTag) {
isThinking = true;
}

Expand All @@ -557,7 +559,7 @@ class Ollama extends BaseLLM implements ModelInstaller {

if (thinkingMessage) {
// could cause issues with termination if chunk doesn't match this exactly
if (content === "</think>") {
if (content === thinkCloseTag) {
isThinking = false;
}
// When Streaming you can't have both thinking and content
Expand Down
19 changes: 15 additions & 4 deletions core/util/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,13 +191,24 @@ export function dedent(strings: TemplateStringsArray, ...values: any[]) {
}

/**
* Removes code blocks from a message.
* Removes code blocks and thinking blocks from a message.
*
* Return modified message text.
* @param text - The message text to process.
* @param thinkTagName - The XML tag name used for thinking output (default: "think").
* Different LLM providers may use different tag names for reasoning output.
* Set this to match your provider's format (e.g. vLLM custom reasoning tags).
* @returns Modified message text with code blocks and think blocks removed.
*/
export function removeCodeBlocksAndTrim(text: string): string {
export function removeCodeBlocksAndTrim(
text: string,
thinkTagName: string = "think",
): string {
const codeBlockRegex = /```[\s\S]*?```/g;
const thinkBlockRegex = /<think>[\s\S]*?<\/think>/g;
// Build regex dynamically based on the configured tag name
const thinkBlockRegex = new RegExp(
`<${thinkTagName}>[\\s\\S]*?<\\/${thinkTagName}>`,
"g",
);

// Remove code blocks and think blocks from the message text
let processedText = text.replace(codeBlockRegex, "");
Expand Down
Loading