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: 6 additions & 0 deletions src/core/transport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export interface StreamRequestOptions {
* - "always" — strip for every model
*/
stripThinkTags?: "never" | "auto" | "always";
/**
* Force think-tag stripping regardless of stripThinkTags mode.
* Set to true when the request is a subagent/tool-call invocation
* to prevent <think> tags in content from rendering as blank code blocks.
*/
forceStripThinkTags?: boolean;
}

export interface TransportRequestSummary {
Expand Down
9 changes: 9 additions & 0 deletions src/provider/OpenCodeProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -901,6 +901,11 @@ export class OpenCodeProvider implements vscode.LanguageModelChatProvider<OpenCo

try {
const contextWindowOutputBuffer = limits.advertisedMaxOutputTokens;
// Subagent/tool-call requests always have tools present. Force
// think-tag stripping for these requests to prevent <think> tags
// in content from rendering as blank code blocks in the chat UI.
const isToolCallRequest = Array.isArray(options.tools) && options.tools.length > 0;
const forceStripThinkTags = isToolCallRequest || undefined;

if (routing.endpointKind === "messages") {
await runStreamAnthropicMessages({
Expand All @@ -921,6 +926,7 @@ export class OpenCodeProvider implements vscode.LanguageModelChatProvider<OpenCo
capacityLimitedModelNotes: CAPACITY_LIMITED_MODEL_NOTES,
onTransportSummary,
stripThinkTags: settings.stripThinkTags,
forceStripThinkTags,
});
return;
}
Expand All @@ -944,6 +950,7 @@ export class OpenCodeProvider implements vscode.LanguageModelChatProvider<OpenCo
capacityLimitedModelNotes: CAPACITY_LIMITED_MODEL_NOTES,
onTransportSummary,
stripThinkTags: settings.stripThinkTags,
forceStripThinkTags,
onReasoningContent: (toolCallIds, reasoningContent) => {
this.storeReasoningContent(toolCallIds, reasoningContent);
},
Expand Down Expand Up @@ -971,6 +978,7 @@ export class OpenCodeProvider implements vscode.LanguageModelChatProvider<OpenCo
capacityLimitedModelNotes: CAPACITY_LIMITED_MODEL_NOTES,
onTransportSummary,
stripThinkTags: settings.stripThinkTags,
forceStripThinkTags,
onReasoningContent: (toolCallIds, reasoningContent) => {
this.storeReasoningContent(toolCallIds, reasoningContent);
},
Expand All @@ -997,6 +1005,7 @@ export class OpenCodeProvider implements vscode.LanguageModelChatProvider<OpenCo
capacityLimitedModelNotes: CAPACITY_LIMITED_MODEL_NOTES,
onTransportSummary,
stripThinkTags: settings.stripThinkTags,
forceStripThinkTags,
treatReasoningAsContent: thinkingProviderFor(rawModelId).treatReasoningAsContent(routing.endpointUrl, settings.thinking),
onReasoningContent: (toolCallIds, reasoningContent) => {
this.storeReasoningContent(toolCallIds, reasoningContent);
Expand Down
2 changes: 1 addition & 1 deletion src/transports/anthropic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { extractAnthropicParts } from "./extract";

/** Anthropic Messages API transport (Claude-family / MiniMax m2.x / Qwen 3.x-plus). */
export async function streamAnthropicMessages(options: StreamRequestOptions): Promise<void> {
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId);
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId, options.forceStripThinkTags);
const extractor = new AnthropicResponseExtractor(
options.onReasoningContent,
createReasoningDebugger(options.output, options.debugReasoning),
Expand Down
2 changes: 1 addition & 1 deletion src/transports/chatCompletions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { reportProgressPart } from "./streamParts";

/** OpenAI-compatible chat-completions transport. */
export async function streamChatCompletions(options: StreamRequestOptions): Promise<void> {
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId);
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId, options.forceStripThinkTags);
// Display decision: whether `reasoning_content` should be surfaced as visible
// text instead of a thinking part. Computed UPSTREAM by the thinking provider
// strategy from the resolved thinking config — not inferred from the body
Expand Down
2 changes: 1 addition & 1 deletion src/transports/google.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { extractChatCompletionParts } from "./extract";

/** Google Generative Language API transport (Gemini via Zen). */
export async function streamGoogleGenerateContent(options: StreamRequestOptions): Promise<void> {
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId);
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId, options.forceStripThinkTags);
const extractor = new OpenAiResponseExtractor(
options.onReasoningContent,
createReasoningDebugger(options.output, options.debugReasoning),
Expand Down
2 changes: 1 addition & 1 deletion src/transports/responses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { extractChatCompletionParts } from "./extract";

/** OpenAI Responses API transport (GPT-family models). */
export async function streamResponsesApi(options: StreamRequestOptions): Promise<void> {
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId);
const thinkFilter = createThinkTagFilter(options.stripThinkTags, options.modelId, options.forceStripThinkTags);
const extractor = new OpenAiResponseExtractor(
options.onReasoningContent,
createReasoningDebugger(options.output, options.debugReasoning),
Expand Down
9 changes: 7 additions & 2 deletions src/transports/thinkTags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ export function shouldStripThinkTags(mode: "never" | "auto" | "always" | undefin
return /^minimax-m/i.test(modelId);
}

export function createThinkTagFilter(mode: "never" | "auto" | "always" | undefined, modelId: string): ThinkTagFilter | undefined {
return shouldStripThinkTags(mode, modelId) ? new ThinkTagFilter() : undefined;
export function createThinkTagFilter(
mode: "never" | "auto" | "always" | undefined,
modelId: string,
forceOverride?: boolean,
): ThinkTagFilter | undefined {
const effective = forceOverride ? "always" : mode;
return shouldStripThinkTags(effective, modelId) ? new ThinkTagFilter() : undefined;
}

export class ThinkTagFilter {
Expand Down
Loading