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
9 changes: 9 additions & 0 deletions extensions/copilot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4045,6 +4045,15 @@
"onExp"
]
},
"github.copilot.chat.responsesApi.persistentCoT.enabled": {
"type": "boolean",
"default": false,
"markdownDescription": "%github.copilot.config.responsesApi.persistentCoT.enabled%",
"tags": [
"experimental",
"onExp"
]
},
"github.copilot.chat.updated53CodexPrompt.enabled": {
"type": "boolean",
"default": true,
Expand Down
1 change: 1 addition & 0 deletions extensions/copilot/package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@
"github.copilot.config.responsesApiReasoningSummary": "Sets the reasoning summary style used for the Responses API. Requires `#github.copilot.chat.useResponsesApi#`.",
"github.copilot.config.responsesApiContextManagement.enabled": "Enables context management for the Responses API. Requires `#github.copilot.chat.useResponsesApi#`.",
"github.copilot.config.responsesApi.promptCacheKey.enabled": "Enables prompt cache key being set for the Responses API.",
"github.copilot.config.responsesApi.persistentCoT.enabled": "Enables persistent chain of thought for supported Responses API models.",
"github.copilot.config.updated53CodexPrompt.enabled": "Enables the updated prompt for gpt-5.3-codex model.",
"github.copilot.config.claude47OpusPrompt.enabled": "Enables the updated system prompt tuned for the Claude Opus 4.7 model.",
"github.copilot.config.gpt55GetChangedFilesTool.enabled": "Enables the Get Changed Files tool for gpt-5.5 models.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -968,6 +968,8 @@ export namespace ConfigKey {
export const ResponsesApiContextManagementEnabled = defineSetting<boolean>('chat.responsesApiContextManagement.enabled', ConfigType.ExperimentBased, false);
/** Enable client-side prompt_cache_key (conversationId:modelFamily) sent to Responses API */
export const ResponsesApiPromptCacheKeyEnabled = defineSetting<boolean>('chat.responsesApi.promptCacheKey.enabled', ConfigType.ExperimentBased, false);
/** Enable persistent chain of thought for supported Responses API model families */
export const ResponsesApiPersistentCoTEnabled = defineSetting<boolean>('chat.responsesApi.persistentCoT.enabled', ConfigType.ExperimentBased, false);
/** Enable updated prompt for 5.3Codex model */
export const Updated53CodexPromptEnabled = defineSetting<boolean>('chat.updated53CodexPrompt.enabled', ConfigType.ExperimentBased, true);
/** Enable updated prompt for Claude Opus 4.7 model */
Expand Down
9 changes: 6 additions & 3 deletions extensions/copilot/src/platform/endpoint/node/responsesApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { IChatWebSocketManager } from '../../networking/node/chatWebSocketManage
import { IExperimentationService } from '../../telemetry/common/nullExperimentationService';
import { ITelemetryService } from '../../telemetry/common/telemetry';
import { TelemetryData } from '../../telemetry/common/telemetryData';
import { getVerbosityForModelSync } from '../common/chatModelCapabilities';
import { getVerbosityForModelSync, isHiddenModelM } from '../common/chatModelCapabilities';
import { rawPartAsCompactionData } from '../common/compactionDataContainer';
import { rawPartAsPhaseData } from '../common/phaseDataContainer';
import { getIndexOfStatefulMarker, getStatefulMarkerAndIndex } from '../common/statefulMarkerContainer';
Expand Down Expand Up @@ -163,10 +163,13 @@ export function createResponsesRequestBody(accessor: ServicesAccessor, options:
? (effortFromSetting || options.modelCapabilities?.reasoningEffort || 'medium')
: undefined;
const summary = summaryConfig === 'off' || shouldDisableReasoningSummary ? undefined : summaryConfig;
if (effort || summary) {
const persistentCoTEnabled = configService.getExperimentBasedConfig(ConfigKey.ResponsesApiPersistentCoTEnabled, expService)
&& isHiddenModelM(endpoint);
if (effort || summary || persistentCoTEnabled) {
body.reasoning = {
...(effort ? { effort } : {}),
...(summary ? { summary } : {})
...(summary ? { summary } : {}),
...(persistentCoTEnabled ? { context: 'all_turns' } : {})
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { describe, expect, it } from 'vitest';
import { TokenizerType } from '../../../../util/common/tokenizer';
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
import { ChatLocation } from '../../../chat/common/commonTypes';
import { ConfigKey, IConfigurationService } from '../../../configuration/common/configurationService';
import { ILogService } from '../../../log/common/logService';
import { isOpenAIContextManagementResponse } from '../../../networking/common/fetch';
import { IChatEndpoint, ICreateEndpointBodyOptions } from '../../../networking/common/networking';
Expand Down Expand Up @@ -368,6 +369,59 @@ describe('responseApiInputToRawMessagesForLogging', () => {
});

describe('createResponsesRequestBody', () => {
it('enables persistent CoT on initial requests for hidden model M when the experiment is enabled', () => {
const services = createPlatformServices();
const accessor = services.createTestingAccessor();
const instantiationService = accessor.get(IInstantiationService);
accessor.get(IConfigurationService).setConfig(ConfigKey.ResponsesApiPersistentCoTEnabled, true);
const endpoint = { ...testEndpoint, family: 'ember-alpha', supportsReasoningEffort: ['low', 'medium', 'high'] };

const body = instantiationService.invokeFunction(servicesAccessor => createResponsesRequestBody(servicesAccessor, createRequestOptions([], false), endpoint.model, endpoint));

expect(body.reasoning).toEqual({ effort: 'medium', summary: 'detailed', context: 'all_turns' });

accessor.dispose();
services.dispose();
});

it('does not enable persistent CoT when the experiment is disabled or the family is unsupported', () => {
const services = createPlatformServices();
const accessor = services.createTestingAccessor();
const instantiationService = accessor.get(IInstantiationService);
const emberEndpoint = { ...testEndpoint, family: 'ember-alpha' };
const unsupportedEndpoint = { ...testEndpoint, model: 'ember-alpha', family: 'other-family' };

const disabledBody = instantiationService.invokeFunction(servicesAccessor => createResponsesRequestBody(servicesAccessor, createRequestOptions([], false), emberEndpoint.model, emberEndpoint));
accessor.get(IConfigurationService).setConfig(ConfigKey.ResponsesApiPersistentCoTEnabled, true);
const unsupportedBody = instantiationService.invokeFunction(servicesAccessor => createResponsesRequestBody(servicesAccessor, createRequestOptions([], false), unsupportedEndpoint.model, unsupportedEndpoint));

expect(disabledBody.reasoning?.context).toBeUndefined();
expect(unsupportedBody.reasoning?.context).toBeUndefined();

accessor.dispose();
services.dispose();
});

it('keeps persistent CoT enabled when continuing from a previous response', () => {
const services = createPlatformServices();
const accessor = services.createTestingAccessor();
const instantiationService = accessor.get(IInstantiationService);
accessor.get(IConfigurationService).setConfig(ConfigKey.ResponsesApiPersistentCoTEnabled, true);
const endpoint = { ...testEndpoint, family: 'ember-alpha' };
const messages: Raw.ChatMessage[] = [
createStatefulMarkerMessage(endpoint.model, 'resp-prev'),
{ role: Raw.ChatRole.User, content: [{ type: Raw.ChatCompletionContentPartKind.Text, text: 'continue' }] },
];

const body = instantiationService.invokeFunction(servicesAccessor => createResponsesRequestBody(servicesAccessor, createRequestOptions(messages, false), endpoint.model, endpoint));

expect(body.previous_response_id).toBe('resp-prev');
expect(body.reasoning?.context).toBe('all_turns');

accessor.dispose();
services.dispose();
});

it('extracts compaction threshold from request body context management', () => {
expect(getResponsesApiCompactionThresholdFromBody({
context_management: [{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export interface IEndpointBody {
prediction?: Prediction;
messages?: any[];
n?: number;
reasoning?: { effort?: string; summary?: string };
reasoning?: { effort?: string; summary?: string; context?: 'current_turn' | 'all_turns' };
tool_choice?: OptionalChatRequestParams['tool_choice'] | { type: 'function'; name: string } | string;
top_logprobs?: number;
intent?: boolean;
Expand Down
Loading