豆豆友情提示:这是一个非官方 GitHub 代理镜像,主要用于网络测试或访问加速。请勿在此进行登录、注册或处理任何敏感信息。进行这些操作请务必访问官方网站 github.com。 Raw 内容也通过此代理提供。
Skip to content

Commit 36da428

Browse files
authored
Attach response output message IDs only to phased assistant messages (#4785)
* Attach response output message IDs only to phased assistant messages * noop * Remove stray probe file * noop2 * Remove stray probe file * noop3 * Remove stray probe file * adding correct messageID for the outgoing messages * adding correct messageID for the outgoing messages * adding correct messageID for the outgoing messages * Update defaultIntentRequestHandler snapshots
1 parent 6ad6a35 commit 36da428

File tree

13 files changed

+327
-14
lines changed

13 files changed

+327
-14
lines changed

src/extension/conversation/vscode-node/languageModelAccess.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import { EmbeddingType, getWellKnownEmbeddingTypeInfo, IEmbeddingsComputer } fro
1616
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1717
import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes';
1818
import { ModelAliasRegistry } from '../../../platform/endpoint/common/modelAliasRegistry';
19+
import { encodePhaseData } from '../../../platform/endpoint/common/phaseDataContainer';
20+
import { encodeResponseOutputMessageId } from '../../../platform/endpoint/common/responseOutputMessageIdContainer';
1921
import { encodeStatefulMarker } from '../../../platform/endpoint/common/statefulMarkerContainer';
2022
import { AutoChatEndpoint } from '../../../platform/endpoint/node/autoChatEndpoint';
2123
import { IAutomodeService } from '../../../platform/endpoint/node/automodeService';
@@ -715,6 +717,20 @@ export class CopilotLanguageModelWrapper extends Disposable {
715717
);
716718
}
717719

720+
if (delta.phase) {
721+
progress.report(
722+
new vscode.LanguageModelDataPart(encodePhaseData({
723+
phase: delta.phase,
724+
}), CustomDataPartMimeTypes.PhaseData)
725+
);
726+
}
727+
728+
if (delta.responseOutputMessageId) {
729+
progress.report(
730+
new vscode.LanguageModelDataPart(encodeResponseOutputMessageId(delta.responseOutputMessageId), CustomDataPartMimeTypes.ResponseOutputMessageId)
731+
);
732+
}
733+
718734
return undefined;
719735
};
720736
return this._provideLanguageModelResponse(endpoint, messages, options, extensionId, finishCallback, token);

src/extension/conversation/vscode-node/languageModelAccessPrompt.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import { AssistantMessage, PromptElement, PromptElementProps, SystemMessage, Too
88
import * as vscode from 'vscode';
99
import { LanguageModelTextPart } from 'vscode';
1010
import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes';
11+
import { decodePhaseData, PhaseDataContainer } from '../../../platform/endpoint/common/phaseDataContainer';
12+
import { decodeResponseOutputMessageId, ResponseOutputMessageIdContainer } from '../../../platform/endpoint/common/responseOutputMessageIdContainer';
1113
import { decodeStatefulMarker, StatefulMarkerContainer } from '../../../platform/endpoint/common/statefulMarkerContainer';
1214
import { ThinkingDataContainer } from '../../../platform/endpoint/common/thinkingDataContainer';
1315
import { SafetyRules } from '../../prompts/node/base/safetyRules';
@@ -35,6 +37,12 @@ export class LanguageModelAccessPrompt extends PromptElement<Props> {
3537
.map(part => part.value).join(''));
3638

3739
} else if (message.role === vscode.LanguageModelChatMessageRole.Assistant) {
40+
const phaseDataPart = message.content.find(part => part instanceof vscode.LanguageModelDataPart && part.mimeType === CustomDataPartMimeTypes.PhaseData) as vscode.LanguageModelDataPart | undefined;
41+
const phaseData = phaseDataPart && decodePhaseData(phaseDataPart.data);
42+
const responseOutputMessageIdPart = message.content.find(part => part instanceof vscode.LanguageModelDataPart && part.mimeType === CustomDataPartMimeTypes.ResponseOutputMessageId) as vscode.LanguageModelDataPart | undefined;
43+
const responseOutputMessageId = responseOutputMessageIdPart
44+
? decodeResponseOutputMessageId(responseOutputMessageIdPart.data)
45+
: phaseData?.responseOutputMessageId;
3846
const statefulMarkerPart = message.content.find(part => part instanceof vscode.LanguageModelDataPart && part.mimeType === CustomDataPartMimeTypes.StatefulMarker) as vscode.LanguageModelDataPart | undefined;
3947
const statefulMarker = statefulMarkerPart && decodeStatefulMarker(statefulMarkerPart.data);
4048
const filteredContent = message.content.filter(part => !(part instanceof vscode.LanguageModelDataPart));
@@ -45,7 +53,9 @@ export class LanguageModelAccessPrompt extends PromptElement<Props> {
4553

4654
const statefulMarkerElement = statefulMarker && <StatefulMarkerContainer statefulMarker={statefulMarker} />;
4755
const thinkingElement = thinking && thinking.id && <ThinkingDataContainer thinking={{ id: thinking.id, text: thinking.value, metadata: thinking.metadata }} />;
48-
chatMessages.push(<AssistantMessage name={message.name} toolCalls={toolCalls.map(tc => ({ id: tc.callId, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.input) } }))}>{statefulMarkerElement}{content?.value}{thinkingElement}</AssistantMessage>);
56+
const responseOutputMessageIdElement = responseOutputMessageId && <ResponseOutputMessageIdContainer responseOutputMessageId={responseOutputMessageId} />;
57+
const phaseElement = phaseData && <PhaseDataContainer phase={phaseData.phase} />;
58+
chatMessages.push(<AssistantMessage name={message.name} toolCalls={toolCalls.map(tc => ({ id: tc.callId, type: 'function', function: { name: tc.name, arguments: JSON.stringify(tc.input) } }))}>{statefulMarkerElement}{thinkingElement}{responseOutputMessageIdElement}{phaseElement}{content?.value}</AssistantMessage>);
4959
} else if (message.role === vscode.LanguageModelChatMessageRole.User) {
5060
for (const part of message.content) {
5161
if (part instanceof vscode.LanguageModelToolResultPart2 || part instanceof vscode.LanguageModelToolResultPart) {

src/extension/intents/node/toolCallingLoop.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,6 +1205,7 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
12051205
let statefulMarker: string | undefined;
12061206
const toolCalls: IToolCall[] = [];
12071207
let thinkingItem: ThinkingDataItem | undefined;
1208+
let responseOutputMessageId: string | undefined;
12081209
const rawEffort = this.options.request.modelConfiguration?.reasoningEffort;
12091210
const reasoningEffort = typeof rawEffort === 'string' ? rawEffort : undefined;
12101211
const shouldDisableThinking = isContinuation && isAnthropicFamily(endpoint) && !ToolCallingLoop.messagesContainThinking(effectiveBuildPromptResult.messages);
@@ -1235,6 +1236,9 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
12351236
if (delta.statefulMarker) {
12361237
statefulMarker = delta.statefulMarker;
12371238
}
1239+
if (delta.responseOutputMessageId) {
1240+
responseOutputMessageId = delta.responseOutputMessageId;
1241+
}
12381242
if (delta.thinking) {
12391243
thinkingItem = ThinkingDataItem.createOrUpdate(thinkingItem, delta.thinking);
12401244
}
@@ -1330,6 +1334,7 @@ export abstract class ToolCallingLoop<TOptions extends IToolCallingLoopOptions =
13301334
response: fetchResult.value,
13311335
toolCalls,
13321336
toolInputRetry,
1337+
responseOutputMessageId,
13331338
statefulMarker,
13341339
thinking: thinkingItem,
13351340
phase,

src/extension/prompt/common/intents.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface IToolCallRound {
3131
toolInputRetry: number;
3232
toolCalls: IToolCall[];
3333
thinking?: ThinkingData;
34+
responseOutputMessageId?: string;
3435
statefulMarker?: string;
3536
/** Compaction data from the Responses API, round-tripped in outgoing requests */
3637
compaction?: OpenAIContextManagementResponse;

src/extension/prompt/common/toolCallRound.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class ToolCallRound implements IToolCallRound {
1818
public summary: string | undefined;
1919
public phase?: string;
2020
public phaseModelId?: string;
21+
public responseOutputMessageId?: string;
2122

2223
/**
2324
* Creates a ToolCallRound from an existing IToolCallRound object.
@@ -29,6 +30,7 @@ export class ToolCallRound implements IToolCallRound {
2930
params.toolCalls,
3031
params.toolInputRetry,
3132
params.id,
33+
params.responseOutputMessageId,
3234
params.statefulMarker,
3335
params.thinking,
3436
params.timestamp,
@@ -45,6 +47,7 @@ export class ToolCallRound implements IToolCallRound {
4547
* @param toolCalls The tool calls made by the assistant
4648
* @param toolInputRetry The number of times this round has been retried due to tool input validation failures
4749
* @param id A stable identifier for this round
50+
* @param responseOutputMessageId Optional message ID from the responses API, used for associating the assistant's response with tool call results
4851
* @param statefulMarker Optional stateful marker used with the responses API
4952
* @param thinking Optional thinking/reasoning data
5053
* @param timestamp Epoch millis when this round started (defaults to `Date.now()`)
@@ -54,11 +57,14 @@ export class ToolCallRound implements IToolCallRound {
5457
public readonly toolCalls: IToolCall[] = [],
5558
public readonly toolInputRetry: number = 0,
5659
public readonly id: string = ToolCallRound.generateID(),
60+
responseOutputMessageId?: string,
5761
public readonly statefulMarker?: string,
5862
public readonly thinking?: ThinkingData,
5963
public readonly timestamp: number = Date.now(),
6064
public readonly compaction?: OpenAIContextManagementResponse,
61-
) { }
65+
) {
66+
this.responseOutputMessageId = responseOutputMessageId;
67+
}
6268

6369
private static generateID(): string {
6470
return generateUuid();

src/extension/prompt/node/test/__snapshots__/defaultIntentRequestHandler.spec.ts.snap

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
2727
"phase": undefined,
2828
"phaseModelId": undefined,
2929
"response": "response number 0",
30+
"responseOutputMessageId": undefined,
3031
"statefulMarker": undefined,
3132
"summary": undefined,
3233
"thinking": undefined,
@@ -46,6 +47,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
4647
"phase": undefined,
4748
"phaseModelId": undefined,
4849
"response": "response number 1",
50+
"responseOutputMessageId": undefined,
4951
"statefulMarker": undefined,
5052
"summary": undefined,
5153
"thinking": undefined,
@@ -65,6 +67,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
6567
"phase": undefined,
6668
"phaseModelId": undefined,
6769
"response": "response number 2",
70+
"responseOutputMessageId": undefined,
6871
"statefulMarker": undefined,
6972
"summary": undefined,
7073
"thinking": undefined,
@@ -103,6 +106,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
103106
"phase": undefined,
104107
"phaseModelId": undefined,
105108
"response": "response number 3",
109+
"responseOutputMessageId": undefined,
106110
"statefulMarker": undefined,
107111
"summary": undefined,
108112
"thinking": undefined,
@@ -122,6 +126,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
122126
"phase": undefined,
123127
"phaseModelId": undefined,
124128
"response": "response number 4",
129+
"responseOutputMessageId": undefined,
125130
"statefulMarker": undefined,
126131
"summary": undefined,
127132
"thinking": undefined,
@@ -141,6 +146,7 @@ exports[`defaultIntentRequestHandler > ChatResult metadata after multiple turns
141146
"phase": undefined,
142147
"phaseModelId": undefined,
143148
"response": "response number 5",
149+
"responseOutputMessageId": undefined,
144150
"statefulMarker": undefined,
145151
"summary": undefined,
146152
"thinking": undefined,
@@ -222,6 +228,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
222228
"phase": undefined,
223229
"phaseModelId": undefined,
224230
"response": "response number 0",
231+
"responseOutputMessageId": undefined,
225232
"statefulMarker": undefined,
226233
"summary": undefined,
227234
"thinking": undefined,
@@ -241,6 +248,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
241248
"phase": undefined,
242249
"phaseModelId": undefined,
243250
"response": "response number 1",
251+
"responseOutputMessageId": undefined,
244252
"statefulMarker": undefined,
245253
"summary": undefined,
246254
"thinking": undefined,
@@ -260,6 +268,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
260268
"phase": undefined,
261269
"phaseModelId": undefined,
262270
"response": "response number 2",
271+
"responseOutputMessageId": undefined,
263272
"statefulMarker": undefined,
264273
"summary": undefined,
265274
"thinking": undefined,
@@ -279,6 +288,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
279288
"phase": undefined,
280289
"phaseModelId": undefined,
281290
"response": "response number 3",
291+
"responseOutputMessageId": undefined,
282292
"statefulMarker": undefined,
283293
"summary": undefined,
284294
"thinking": undefined,
@@ -340,6 +350,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
340350
"phase": undefined,
341351
"phaseModelId": undefined,
342352
"response": "response number 4",
353+
"responseOutputMessageId": undefined,
343354
"statefulMarker": undefined,
344355
"summary": undefined,
345356
"thinking": undefined,
@@ -359,6 +370,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
359370
"phase": undefined,
360371
"phaseModelId": undefined,
361372
"response": "response number 5",
373+
"responseOutputMessageId": undefined,
362374
"statefulMarker": undefined,
363375
"summary": undefined,
364376
"thinking": undefined,
@@ -378,6 +390,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
378390
"phase": undefined,
379391
"phaseModelId": undefined,
380392
"response": "response number 6",
393+
"responseOutputMessageId": undefined,
381394
"statefulMarker": undefined,
382395
"summary": undefined,
383396
"thinking": undefined,
@@ -397,6 +410,7 @@ exports[`defaultIntentRequestHandler > confirms on max tool call iterations, and
397410
"phase": undefined,
398411
"phaseModelId": undefined,
399412
"response": "response number 7",
413+
"responseOutputMessageId": undefined,
400414
"statefulMarker": undefined,
401415
"summary": undefined,
402416
"thinking": undefined,
@@ -2539,6 +2553,7 @@ exports[`defaultIntentRequestHandler > makes a successful request with a single
25392553
"phase": undefined,
25402554
"phaseModelId": undefined,
25412555
"response": "some response here :)",
2556+
"responseOutputMessageId": undefined,
25422557
"statefulMarker": undefined,
25432558
"summary": undefined,
25442559
"thinking": undefined,
@@ -2871,6 +2886,7 @@ exports[`defaultIntentRequestHandler > makes a tool call turn 1`] = `
28712886
"phase": undefined,
28722887
"phaseModelId": undefined,
28732888
"response": "some response here :)",
2889+
"responseOutputMessageId": undefined,
28742890
"statefulMarker": undefined,
28752891
"summary": undefined,
28762892
"thinking": undefined,
@@ -2890,6 +2906,7 @@ exports[`defaultIntentRequestHandler > makes a tool call turn 1`] = `
28902906
"phase": undefined,
28912907
"phaseModelId": undefined,
28922908
"response": "response to tool call",
2909+
"responseOutputMessageId": undefined,
28932910
"statefulMarker": undefined,
28942911
"summary": undefined,
28952912
"thinking": undefined,

src/extension/prompts/node/panel/toolCalling.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import { CompactionDataContainer } from '../../../../platform/endpoint/common/co
1515
import { IEndpointProvider } from '../../../../platform/endpoint/common/endpointProvider';
1616
import { CacheType } from '../../../../platform/endpoint/common/endpointTypes';
1717
import { PhaseDataContainer } from '../../../../platform/endpoint/common/phaseDataContainer';
18+
import { ResponseOutputMessageIdContainer } from '../../../../platform/endpoint/common/responseOutputMessageIdContainer';
1819
import { StatefulMarkerContainer } from '../../../../platform/endpoint/common/statefulMarkerContainer';
1920
import { ThinkingDataContainer } from '../../../../platform/endpoint/common/thinkingDataContainer';
2021
import { IFileSystemService } from '../../../../platform/filesystem/common/fileSystemService';
@@ -118,12 +119,14 @@ export class ChatToolCalls extends PromptElement<ChatToolCallsProps, void> {
118119
// Don't include this when rendering and triggering summarization
119120
const statefulMarker = round.statefulMarker && <StatefulMarkerContainer statefulMarker={{ modelId: this.promptEndpoint.model, marker: round.statefulMarker }} />;
120121
const thinking = (!this.props.isHistorical) && round.thinking && <ThinkingDataContainer thinking={round.thinking} />;
122+
const responseOutputMessageId = round.responseOutputMessageId && <ResponseOutputMessageIdContainer responseOutputMessageId={round.responseOutputMessageId} />;
121123
const phase = (round.phase && round.phaseModelId === this.promptEndpoint.model) ? <PhaseDataContainer phase={round.phase} /> : undefined;
122124
const compaction = round.compaction && <CompactionDataContainer compaction={round.compaction} />;
123125
children.push(
124126
<AssistantMessage toolCalls={assistantToolCalls}>
125127
{statefulMarker}
126128
{thinking}
129+
{responseOutputMessageId}
127130
{phase}
128131
{compaction}
129132
{round.response}

src/platform/endpoint/common/endpointTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export namespace CustomDataPartMimeTypes {
99
export const ThinkingData = 'thinking';
1010
export const ContextManagement = 'context_management';
1111
export const PhaseData = 'phase_data';
12+
export const ResponseOutputMessageId = 'response_output_message_id';
1213
}
1314

1415
export const CacheType = 'ephemeral';

src/platform/endpoint/common/phaseDataContainer.tsx

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@
55
import { BasePromptElementProps, PromptElement, Raw } from '@vscode/prompt-tsx';
66
import { CustomDataPartMimeTypes } from './endpointTypes';
77

8-
interface IPhaseDataOpaque {
9-
type: typeof CustomDataPartMimeTypes.PhaseData;
8+
export interface IPhaseData {
109
phase: string;
10+
responseOutputMessageId?: string;
11+
}
12+
13+
interface IPhaseDataOpaque extends IPhaseData {
14+
type: typeof CustomDataPartMimeTypes.PhaseData;
1115
}
1216

1317
export interface IPhaseDataContainerProps extends BasePromptElementProps {
@@ -27,17 +31,41 @@ export class PhaseDataContainer extends PromptElement<IPhaseDataContainerProps>
2731
}
2832

2933
/**
30-
* Attempts to parse a Raw opaque content part into a phase string, if the type matches.
34+
* Attempts to parse a Raw opaque content part into phase metadata, if the type matches.
3135
*/
32-
export function rawPartAsPhaseData(part: Raw.ChatCompletionContentPartOpaque): string | undefined {
36+
export function rawPartAsPhaseData(part: Raw.ChatCompletionContentPartOpaque): IPhaseData | undefined {
3337
const value = part.value as unknown;
3438
if (!value || typeof value !== 'object') {
3539
return;
3640
}
3741

3842
const data = value as IPhaseDataOpaque;
3943
if (data.type === CustomDataPartMimeTypes.PhaseData && typeof data.phase === 'string') {
40-
return data.phase;
44+
return {
45+
phase: data.phase,
46+
responseOutputMessageId: typeof data.responseOutputMessageId === 'string' ? data.responseOutputMessageId : undefined,
47+
};
4148
}
4249
return;
4350
}
51+
52+
export function encodePhaseData(phaseData: IPhaseData): Uint8Array {
53+
return new TextEncoder().encode(JSON.stringify(phaseData));
54+
}
55+
56+
export function decodePhaseData(data: Uint8Array): IPhaseData {
57+
const decoded = new TextDecoder().decode(data);
58+
try {
59+
const parsed = JSON.parse(decoded) as Partial<IPhaseData>;
60+
if (typeof parsed.phase === 'string') {
61+
return {
62+
phase: parsed.phase,
63+
responseOutputMessageId: typeof parsed.responseOutputMessageId === 'string' ? parsed.responseOutputMessageId : undefined,
64+
};
65+
}
66+
} catch {
67+
// Backward compatibility with older data parts that encoded only the phase string.
68+
}
69+
70+
return { phase: decoded };
71+
}

0 commit comments

Comments
 (0)