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
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ export class ClaudeLanguageModelServer extends Disposable {
messages: messagesForLogging,
finishedCb: async () => undefined,
location: ChatLocation.MessagesProxy,
modelCapabilities: { enableThinking: true },
enableThinking: true,
userInitiatedRequest: isUserInitiatedMessage
}, tokenSource.token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -626,9 +626,7 @@ export class CopilotLanguageModelWrapper extends Disposable {
requestOptions: options,
userInitiatedRequest: !!extensionId,
telemetryProperties,
modelCapabilities: {
reasoningEffort: typeof _options.modelConfiguration?.reasoningEffort === 'string' ? _options.modelConfiguration.reasoningEffort : undefined,
},
reasoningEffort: typeof _options.modelConfiguration?.reasoningEffort === 'string' ? _options.modelConfiguration.reasoningEffort : undefined,
}, token);

// Run request within the parent OTel context (no extra span) so chat spans in chatMLFetcher inherit the agent trace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export class OpenAILanguageModelServer extends Disposable {
messages: messagesForLogging,
finishedCb: async () => undefined,
location: ChatLocation.ResponsesProxy,
modelCapabilities: { enableThinking: true },
enableThinking: true,
userInitiatedRequest: isUserInitiatedMessage
}, tokenSource.token);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export interface IToolCallingBuiltPromptEvent {
tools: LanguageModelToolInformation[];
}

export type ToolCallingLoopFetchOptions = Required<Pick<IMakeChatRequestOptions, 'messages' | 'finishedCb' | 'requestOptions' | 'userInitiatedRequest' | 'turnId'>> & Pick<IMakeChatRequestOptions, 'modelCapabilities'>;
export type ToolCallingLoopFetchOptions = Required<Pick<IMakeChatRequestOptions, 'messages' | 'finishedCb' | 'requestOptions' | 'userInitiatedRequest' | 'turnId'>> & Pick<IMakeChatRequestOptions, 'enableThinking' | 'reasoningEffort'>;

interface StartHookResult {
/**
Expand Down Expand Up @@ -1415,6 +1415,8 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
let statefulMarker: string | undefined;
const toolCalls: IToolCall[] = [];
let thinkingItem: ThinkingDataItem | undefined;
const rawEffort = this.options.request.modelConfiguration?.reasoningEffort;
const reasoningEffort = typeof rawEffort === 'string' ? rawEffort : undefined;
const shouldDisableThinking = isContinuation && isAnthropicFamily(endpoint) && !ToolCallingLoop.messagesContainThinking(effectiveBuildPromptResult.messages);
const enableThinking = !shouldDisableThinking;
let phase: string | undefined;
Expand Down Expand Up @@ -1466,9 +1468,8 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
})),
},
userInitiatedRequest: (iterationNumber === 0 && !isContinuation && !this.options.request.subAgentInvocationId && !this.options.request.isSystemInitiated) || this.stopHookUserInitiated,
modelCapabilities: {
enableThinking,
},
enableThinking,
reasoningEffort,
}, token).finally(() => {
this.stopHookUserInitiated = false;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { IGitService } from '../../../platform/git/common/gitService';
import { IOctoKitService } from '../../../platform/github.qkg1.topmon/githubService';
import { HAS_IGNORED_FILES_MESSAGE } from '../../../platform/ignore/common/ignoreService';
import { ILogService } from '../../../platform/log/common/logService';
import { isAnthropicContextEditingEnabled, isAnthropicToolSearchEnabled } from '../../../platform/networking/common/anthropic';
import { isAnthropicToolSearchEnabled } from '../../../platform/networking/common/anthropic';
import { FilterReason } from '../../../platform/networking/common/openai';
import { IOTelService } from '../../../platform/otel/common/otelService';
import { CapturingToken } from '../../../platform/requestLogger/common/capturingToken';
Expand Down Expand Up @@ -693,18 +693,9 @@ class DefaultToolCallingLoop extends ToolCallingLoop<IDefaultToolLoopOptions> {
const debugName = this._isInlineSummarizationRequest ? 'inlineSummarizeConversationHistory-full' : baseDebugName;
const location = this.options.overrideRequestLocation ?? this.options.location;
const isThinkingLocation = location === ChatLocation.Agent || location === ChatLocation.MessagesProxy;
const rawEffort = this.options.request.modelConfiguration?.reasoningEffort;
const reasoningEffort = typeof rawEffort === 'string' ? rawEffort : undefined;
const isSubagent = !!this.options.request.subAgentInvocationId;
return this.options.invocation.endpoint.makeChatRequest2({
...opts,
modelCapabilities: {
...opts.modelCapabilities,
enableThinking: isThinkingLocation && opts.modelCapabilities?.enableThinking,
reasoningEffort,
enableToolSearch: !isSubagent && isAnthropicToolSearchEnabled(this.options.invocation.endpoint, this._configurationService),
enableContextEditing: !isSubagent && isAnthropicContextEditingEnabled(this.options.invocation.endpoint, this._configurationService, this._experimentationService),
},
enableThinking: isThinkingLocation && opts.enableThinking,
debugName,
conversationId: this.options.conversation.sessionId,
turnId: opts.turnId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,15 @@ export class ExecutionSubagentToolCallingLoop extends ToolCallingLoop<IExecution
return allTools.filter(tool => allowedExecutionTools.has(tool.name as ToolName));
}

protected async fetch({ messages, finishedCb, requestOptions, modelCapabilities }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
protected async fetch({ messages, finishedCb, requestOptions, enableThinking, reasoningEffort }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
const endpoint = await this.getEndpoint();
return endpoint.makeChatRequest2({
debugName: ExecutionSubagentToolCallingLoop.ID,
messages,
finishedCb,
location: this.options.location,
modelCapabilities,
enableThinking,
reasoningEffort,
requestOptions: {
...(requestOptions ?? {}),
temperature: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,15 @@ export class SearchSubagentToolCallingLoop extends ToolCallingLoop<ISearchSubage
return allTools.filter(tool => allowedSearchTools.has(tool.name as ToolName));
}

protected async fetch({ messages, finishedCb, requestOptions, modelCapabilities }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
protected async fetch({ messages, finishedCb, requestOptions, enableThinking, reasoningEffort }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
const endpoint = await this.getEndpoint();
return endpoint.makeChatRequest2({
debugName: SearchSubagentToolCallingLoop.ID,
messages,
finishedCb,
location: this.options.location,
modelCapabilities,
enableThinking,
reasoningEffort,
requestOptions: {
...requestOptions,
temperature: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/

import type * as vscode from 'vscode';
import { TOOL_SEARCH_SUPPORTED_MODELS } from '../../../platform/endpoint/common/chatModelCapabilities';
import { ILogService } from '../../../platform/log/common/logService';
import { CUSTOM_TOOL_SEARCH_NAME } from '../../../platform/networking/common/anthropic';
import { CUSTOM_TOOL_SEARCH_NAME, TOOL_SEARCH_SUPPORTED_MODELS } from '../../../platform/networking/common/anthropic';
import { LanguageModelTextPart, LanguageModelToolResult } from '../../../vscodeTypes';
import { ICopilotModelSpecificTool, ToolRegistry } from '../common/toolsRegistry';
import { IToolsService } from '../common/toolsService';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -379,47 +379,3 @@ export function getVerbosityForModelSync(model: IChatEndpoint): 'low' | 'medium'

return undefined;
}

/** Model ID prefixes that support the tool search tool. */
export const TOOL_SEARCH_SUPPORTED_MODELS = [
'claude-sonnet-4.5',
'claude-sonnet-4.6',
'claude-opus-4.5',
'claude-opus-4.6',
] as const;

/**
* Returns true if the model supports the tool search tool.
* Provider-agnostic: add additional model prefixes here as other providers adopt tool search.
*/
export function modelSupportsToolSearch(modelId: string): boolean {
return TOOL_SEARCH_SUPPORTED_MODELS.some(prefix => modelId.toLowerCase().startsWith(prefix));
}

/**
* Context editing is supported by:
* - Claude Haiku 4.5 (claude-haiku-4-5-* or claude-haiku-4.5-*)
* - Claude Sonnet 4.6 (claude-sonnet-4-6-* or claude-sonnet-4.6-*)
* - Claude Sonnet 4.5 (claude-sonnet-4-5-* or claude-sonnet-4.5-*)
* - Claude Sonnet 4 (claude-sonnet-4-*)
* - Claude Opus 4.6 (claude-opus-4-6-* or claude-opus-4.6-*)
* - Claude Opus 4.5 (claude-opus-4-5-* or claude-opus-4.5-*)
* - Claude Opus 4.1 (claude-opus-4-1-* or claude-opus-4.1-*)
* - Claude Opus 4 (claude-opus-4-*)
* Provider-agnostic: add additional model prefixes here as other providers adopt context editing.
*/
export function modelSupportsContextEditing(modelId: string): boolean {
const normalized = modelId.toLowerCase().replace(/\./g, '-');
// The 1M context variant doesn't need context editing
if (normalized.includes('1m')) {
return false;
}
return normalized.startsWith('claude-haiku-4-5') ||
normalized.startsWith('claude-sonnet-4-6') ||
normalized.startsWith('claude-sonnet-4-5') ||
normalized.startsWith('claude-sonnet-4') ||
normalized.startsWith('claude-opus-4-6') ||
normalized.startsWith('claude-opus-4-5') ||
normalized.startsWith('claude-opus-4-1') ||
normalized.startsWith('claude-opus-4');
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,6 @@ export type IChatModelCapabilities = {
max_thinking_budget?: number;
min_thinking_budget?: number;
reasoning_effort?: string[];
tool_search?: boolean;
context_editing?: boolean;
};
};

Expand Down
32 changes: 17 additions & 15 deletions extensions/copilot/src/platform/endpoint/node/chatEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import { ITelemetryService, TelemetryProperties } from '../../telemetry/common/t
import { TelemetryData } from '../../telemetry/common/telemetryData';
import { ITokenizerProvider } from '../../tokenizer/node/tokenizer';
import { ICAPIClientService } from '../common/capiClient';
import { isGeminiFamily, modelSupportsContextEditing, modelSupportsToolSearch } from '../common/chatModelCapabilities';
import { isGeminiFamily } from '../common/chatModelCapabilities';
import { IDomainService } from '../common/domainService';
import { CustomModel, IChatModelInformation, ModelSupportedEndpoint } from '../common/endpointProvider';
import { createMessagesRequestBody, processResponseFromMessagesEndpoint } from './messagesApi';
Expand Down Expand Up @@ -129,8 +129,6 @@ export class ChatEndpoint implements IChatEndpoint {
public readonly minThinkingBudget?: number;
public readonly maxThinkingBudget?: number;
public readonly supportsReasoningEffort?: string[];
public readonly supportsToolSearch?: boolean;
public readonly supportsContextEditing?: boolean;
public readonly isPremium?: boolean | undefined;
public readonly multiplier?: number | undefined;
public readonly restrictedToSkus?: string[] | undefined;
Expand Down Expand Up @@ -172,8 +170,6 @@ export class ChatEndpoint implements IChatEndpoint {
this.minThinkingBudget = modelMetadata.capabilities.supports.min_thinking_budget;
this.maxThinkingBudget = modelMetadata.capabilities.supports.max_thinking_budget;
this.supportsReasoningEffort = modelMetadata.capabilities.supports.reasoning_effort;
this.supportsToolSearch = modelMetadata.capabilities.supports.tool_search ?? modelSupportsToolSearch(this.model);
this.supportsContextEditing = modelMetadata.capabilities.supports.context_editing ?? modelSupportsContextEditing(this.model);
this._supportsStreaming = !!modelMetadata.capabilities.supports.streaming;
this.customModel = modelMetadata.custom_model;
this.maxPromptImages = modelMetadata.capabilities.limits?.vision?.max_prompt_images;
Expand All @@ -183,29 +179,35 @@ export class ChatEndpoint implements IChatEndpoint {
// so getExtraHeaders can gate the interleaved-thinking header on whether thinking is actually enabled for the
// request, rather than using the location check. Once plumbed, replace isAllowedConversationAgentModel with
// an enableThinking check for the thinking header (keep location gate for context management / tool search).
public getExtraHeaders(_location?: ChatLocation): Record<string, string> {
public getExtraHeaders(location?: ChatLocation): Record<string, string> {
const headers: Record<string, string> = { ...this.modelMetadata.requestHeaders };

if (this.useMessagesApi) {
const isAllowedConversationAgentModel = location === ChatLocation.Agent || location === ChatLocation.MessagesProxy;
if (isAllowedConversationAgentModel && this.useMessagesApi) {

const modelProviderPreference = this._configurationService.getConfig(ConfigKey.TeamInternal.ModelProviderPreference);
if (modelProviderPreference) {
headers['X-Model-Provider-Preference'] = modelProviderPreference;
}

const betas: string[] = [];
const betaFeatures: string[] = [];

if (!this.supportsAdaptiveThinking) {
betas.push('interleaved-thinking-2025-05-14');
betaFeatures.push('interleaved-thinking-2025-05-14');
}
if (isAnthropicToolSearchEnabled(this, this._configurationService)) {
betas.push('advanced-tool-use-2025-11-20');

// Add context management beta if enabled (required for context editing)
if (isAnthropicContextEditingEnabled(this.model, this._configurationService, this._expService)) {
betaFeatures.push('context-management-2025-06-27');
}
if (isAnthropicContextEditingEnabled(this, this._configurationService, this._expService)) {
betas.push('context-management-2025-06-27');

// Add tool search beta if enabled
if (isAnthropicToolSearchEnabled(this.model, this._configurationService)) {
betaFeatures.push('advanced-tool-use-2025-11-20');
}
if (betas.length > 0) {
headers['anthropic-beta'] = betas.join(',');

if (betaFeatures.length > 0) {
headers['anthropic-beta'] = betaFeatures.join(',');
}
}

Expand Down
13 changes: 8 additions & 5 deletions extensions/copilot/src/platform/endpoint/node/messagesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I

const toolSearchEnabled = isAnthropicToolSearchEnabled(endpoint, configurationService);
const customToolSearchEnabled = isAnthropicCustomToolSearchEnabled(endpoint, configurationService, experimentationService);
const isAllowedConversationAgent = options.location === ChatLocation.Agent || options.location === ChatLocation.MessagesProxy;
// TODO: Use a dedicated flag on options instead of relying on telemetry subType
const isSubagent = options.telemetryProperties?.subType?.startsWith('subagent') ?? false;

// Split tools into non-deferred and deferred up front so we can build finalTools
// with non-deferred first. This ensures the cache_control breakpoint on the last
Expand All @@ -115,7 +118,7 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I
if (!tool.function.name || tool.function.name.length === 0) {
continue;
}
const isDeferred = options.modelCapabilities?.enableToolSearch && toolSearchEnabled && !toolDeferralService.isNonDeferredTool(tool.function.name);
const isDeferred = toolSearchEnabled && isAllowedConversationAgent && !isSubagent && !toolDeferralService.isNonDeferredTool(tool.function.name);
const anthropicTool: AnthropicMessagesTool = {
name: tool.function.name,
description: tool.function.description || '',
Expand All @@ -128,7 +131,7 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I

// Build final tools array, adding tool search tool if enabled
const finalTools: AnthropicMessagesTool[] = [];
if (options.modelCapabilities?.enableToolSearch && toolSearchEnabled && !customToolSearchEnabled) {
if (isAllowedConversationAgent && !isSubagent && toolSearchEnabled && !customToolSearchEnabled) {
// Server-side tool search: use the built-in tool_search_tool_regex
finalTools.push({ name: TOOL_SEARCH_TOOL_NAME, type: TOOL_SEARCH_TOOL_TYPE, defer_loading: false });
}
Expand All @@ -141,9 +144,9 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I
// Thinking is enabled only when options.enableThinking is true, a non-zero thinking budget
// is configured for the model, and the model supports thinking. reasoningEffort (if present)
// is used only to configure the effort level when thinking is enabled, not to gate it.
const reasoningEffort = options.modelCapabilities?.reasoningEffort;
const reasoningEffort = options.reasoningEffort;
let thinkingConfig: { type: 'enabled' | 'adaptive'; budget_tokens?: number } | undefined;
if (options.modelCapabilities?.enableThinking) {
if (options.enableThinking) {
const configuredBudget = configurationService.getConfig(ConfigKey.AnthropicThinkingBudget);
const thinkingExplicitlyDisabled = configuredBudget === 0;
if (endpoint.supportsAdaptiveThinking && !thinkingExplicitlyDisabled) {
Expand Down Expand Up @@ -174,7 +177,7 @@ export function createMessagesRequestBody(accessor: ServicesAccessor, options: I
}

// Build context management configuration
const contextManagement = options.modelCapabilities?.enableContextEditing && isAnthropicContextEditingEnabled(endpoint, configurationService, experimentationService)
const contextManagement = isAllowedConversationAgent && !isSubagent && isAnthropicContextEditingEnabled(endpoint, configurationService, experimentationService)
? getContextManagementFromConfig(configurationService, experimentationService, thinkingEnabled)
: undefined;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export function createResponsesRequestBody(accessor: ServicesAccessor, options:
const shouldDisableReasoningSummary = endpoint.family === 'gpt-5.3-codex-spark-preview';
const effortFromSetting = configService.getConfig(ConfigKey.TeamInternal.ResponsesApiReasoningEffort);
const effort = endpoint.supportsReasoningEffort?.length
? (effortFromSetting || options.modelCapabilities?.reasoningEffort || 'medium')
? (effortFromSetting || options.reasoningEffort || 'medium')
: undefined;
const summary = summaryConfig === 'off' || shouldDisableReasoningSummary ? undefined : summaryConfig;
if (effort || summary) {
Expand Down
Loading
Loading