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

Commit a29de8f

Browse files
committed
adding correct messageID for the outgoing messages
1 parent 0bd133c commit a29de8f

File tree

7 files changed

+89
-59
lines changed

7 files changed

+89
-59
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { IEndpointProvider } from '../../../platform/endpoint/common/endpointPro
1717
import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes';
1818
import { ModelAliasRegistry } from '../../../platform/endpoint/common/modelAliasRegistry';
1919
import { encodePhaseData } from '../../../platform/endpoint/common/phaseDataContainer';
20+
import { encodeResponseOutputMessageId } from '../../../platform/endpoint/common/responseOutputMessageIdContainer';
2021
import { encodeStatefulMarker } from '../../../platform/endpoint/common/statefulMarkerContainer';
2122
import { AutoChatEndpoint } from '../../../platform/endpoint/node/autoChatEndpoint';
2223
import { IAutomodeService } from '../../../platform/endpoint/node/automodeService';
@@ -720,11 +721,16 @@ export class CopilotLanguageModelWrapper extends Disposable {
720721
progress.report(
721722
new vscode.LanguageModelDataPart(encodePhaseData({
722723
phase: delta.phase,
723-
responseOutputMessageId: delta.responseOutputMessageId,
724724
}), CustomDataPartMimeTypes.PhaseData)
725725
);
726726
}
727727

728+
if (delta.responseOutputMessageId) {
729+
progress.report(
730+
new vscode.LanguageModelDataPart(encodeResponseOutputMessageId(delta.responseOutputMessageId), CustomDataPartMimeTypes.ResponseOutputMessageId)
731+
);
732+
}
733+
728734
return undefined;
729735
};
730736
return this._provideLanguageModelResponse(endpoint, messages, options, extensionId, finishCallback, token);

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import * as vscode from 'vscode';
99
import { LanguageModelTextPart } from 'vscode';
1010
import { CustomDataPartMimeTypes } from '../../../platform/endpoint/common/endpointTypes';
1111
import { decodePhaseData, PhaseDataContainer } from '../../../platform/endpoint/common/phaseDataContainer';
12+
import { decodeResponseOutputMessageId, ResponseOutputMessageIdContainer } from '../../../platform/endpoint/common/responseOutputMessageIdContainer';
1213
import { decodeStatefulMarker, StatefulMarkerContainer } from '../../../platform/endpoint/common/statefulMarkerContainer';
1314
import { ThinkingDataContainer } from '../../../platform/endpoint/common/thinkingDataContainer';
1415
import { SafetyRules } from '../../prompts/node/base/safetyRules';
@@ -38,6 +39,10 @@ export class LanguageModelAccessPrompt extends PromptElement<Props> {
3839
} else if (message.role === vscode.LanguageModelChatMessageRole.Assistant) {
3940
const phaseDataPart = message.content.find(part => part instanceof vscode.LanguageModelDataPart && part.mimeType === CustomDataPartMimeTypes.PhaseData) as vscode.LanguageModelDataPart | undefined;
4041
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;
4146
const statefulMarkerPart = message.content.find(part => part instanceof vscode.LanguageModelDataPart && part.mimeType === CustomDataPartMimeTypes.StatefulMarker) as vscode.LanguageModelDataPart | undefined;
4247
const statefulMarker = statefulMarkerPart && decodeStatefulMarker(statefulMarkerPart.data);
4348
const filteredContent = message.content.filter(part => !(part instanceof vscode.LanguageModelDataPart));
@@ -48,8 +53,9 @@ export class LanguageModelAccessPrompt extends PromptElement<Props> {
4853

4954
const statefulMarkerElement = statefulMarker && <StatefulMarkerContainer statefulMarker={statefulMarker} />;
5055
const thinkingElement = thinking && thinking.id && <ThinkingDataContainer thinking={{ id: thinking.id, text: thinking.value, metadata: thinking.metadata }} />;
51-
const phaseElement = phaseData && <PhaseDataContainer phase={phaseData.phase} responseOutputMessageId={phaseData.responseOutputMessageId} />;
52-
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}{phaseElement}{content?.value}</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>);
5359
} else if (message.role === vscode.LanguageModelChatMessageRole.User) {
5460
for (const part of message.content) {
5561
if (part instanceof vscode.LanguageModelToolResultPart2 || part instanceof vscode.LanguageModelToolResultPart) {

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

Lines changed: 4 additions & 1 deletion
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';
@@ -117,12 +118,14 @@ export class ChatToolCalls extends PromptElement<ChatToolCallsProps, void> {
117118
// Don't include this when rendering and triggering summarization
118119
const statefulMarker = round.statefulMarker && <StatefulMarkerContainer statefulMarker={{ modelId: this.promptEndpoint.model, marker: round.statefulMarker }} />;
119120
const thinking = (!this.props.isHistorical) && round.thinking && <ThinkingDataContainer thinking={round.thinking} />;
120-
const phase = (round.phase && round.phaseModelId === this.promptEndpoint.model) ? <PhaseDataContainer phase={round.phase} responseOutputMessageId={round.responseOutputMessageId} /> : undefined;
121+
const responseOutputMessageId = round.responseOutputMessageId && <ResponseOutputMessageIdContainer responseOutputMessageId={round.responseOutputMessageId} />;
122+
const phase = (round.phase && round.phaseModelId === this.promptEndpoint.model) ? <PhaseDataContainer phase={round.phase} /> : undefined;
121123
const compaction = round.compaction && <CompactionDataContainer compaction={round.compaction} />;
122124
children.push(
123125
<AssistantMessage toolCalls={assistantToolCalls}>
124126
{statefulMarker}
125127
{thinking}
128+
{responseOutputMessageId}
126129
{phase}
127130
{compaction}
128131
{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: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ interface IPhaseDataOpaque extends IPhaseData {
1616

1717
export interface IPhaseDataContainerProps extends BasePromptElementProps {
1818
phase: string;
19-
responseOutputMessageId?: string;
2019
}
2120

2221
/**
@@ -25,8 +24,8 @@ export interface IPhaseDataContainerProps extends BasePromptElementProps {
2524
*/
2625
export class PhaseDataContainer extends PromptElement<IPhaseDataContainerProps> {
2726
render() {
28-
const { phase, responseOutputMessageId } = this.props;
29-
const container: IPhaseDataOpaque = { type: CustomDataPartMimeTypes.PhaseData, phase, responseOutputMessageId };
27+
const { phase } = this.props;
28+
const container: IPhaseDataOpaque = { type: CustomDataPartMimeTypes.PhaseData, phase };
3029
return <opaque value={container} />;
3130
}
3231
}

src/platform/endpoint/node/responsesApi.ts

Lines changed: 28 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ import { ITelemetryService } from '../../telemetry/common/telemetry';
2525
import { TelemetryData } from '../../telemetry/common/telemetryData';
2626
import { getVerbosityForModelSync } from '../common/chatModelCapabilities';
2727
import { rawPartAsCompactionData } from '../common/compactionDataContainer';
28-
import { IPhaseData, rawPartAsPhaseData } from '../common/phaseDataContainer';
28+
import { rawPartAsPhaseData } from '../common/phaseDataContainer';
29+
import { rawPartAsResponseOutputMessageId } from '../common/responseOutputMessageIdContainer';
2930
import { getStatefulMarkerAndIndex } from '../common/statefulMarkerContainer';
3031
import { rawPartAsThinkingData } from '../common/thinkingDataContainer';
3132

@@ -120,31 +121,17 @@ function rawMessagesToResponseAPI(modelId: string, messages: readonly Raw.ChatMe
120121
if (message.content.length) {
121122
input.push(...extractCompactionData(message.content));
122123
input.push(...extractThinkingData(message.content));
123-
const phaseData = extractPhaseData(message.content);
124-
const phase = phaseData?.phase;
125-
if (phase) {
126-
const asstContent = message.content.map(rawContentToResponsesOutputContent).filter(isDefined);
127-
if (asstContent.length) {
128-
const assistantMessage: ResponseOutputMessageWithPhase = {
129-
role: 'assistant',
130-
content: asstContent,
131-
id: phaseData?.responseOutputMessageId ?? generateUuid(),
132-
status: 'completed',
133-
type: 'message',
134-
phase,
135-
};
136-
input.push(assistantMessage);
137-
}
138-
} else {
139-
const asstContent = message.content.map(rawContentToResponsesAssistantInputContent).filter(isDefined);
140-
if (asstContent.length) {
141-
const assistantMessage: OpenAI.Responses.EasyInputMessage = {
142-
role: 'assistant',
143-
content: asstContent,
144-
type: 'message',
145-
};
146-
input.push(assistantMessage);
147-
}
124+
const asstContent = message.content.map(rawContentToResponsesOutputContent).filter(isDefined);
125+
if (asstContent.length) {
126+
const assistantMessage: ResponseOutputMessageWithPhase = {
127+
role: 'assistant',
128+
content: asstContent,
129+
id: extractResponseOutputMessageId(message.content) ?? generateUuid(),
130+
status: 'completed',
131+
type: 'message',
132+
phase: extractPhaseData(message.content),
133+
};
134+
input.push(assistantMessage);
148135
}
149136
}
150137
if (message.toolCalls) {
@@ -223,24 +210,6 @@ function rawContentToResponsesOutputContent(part: Raw.ChatCompletionContentPart)
223210
}
224211
}
225212

226-
function rawContentToResponsesAssistantInputContent(part: Raw.ChatCompletionContentPart): OpenAI.Responses.ResponseInputContent | undefined {
227-
switch (part.type) {
228-
case Raw.ChatCompletionContentPartKind.Text:
229-
if (part.text.trim()) {
230-
return { type: 'input_text', text: part.text };
231-
}
232-
return undefined;
233-
case Raw.ChatCompletionContentPartKind.Image:
234-
return { type: 'input_image', detail: part.imageUrl.detail || 'auto', image_url: part.imageUrl.url };
235-
case Raw.ChatCompletionContentPartKind.Opaque: {
236-
const maybeCast = part.value as OpenAI.Responses.ResponseInputContent;
237-
if (maybeCast.type === 'input_text' || maybeCast.type === 'input_image' || maybeCast.type === 'input_file') {
238-
return maybeCast;
239-
}
240-
}
241-
}
242-
}
243-
244213
function extractThinkingData(content: Raw.ChatCompletionContentPart[]): OpenAI.Responses.ResponseReasoningItem[] {
245214
return coalesce(content.map(part => {
246215
if (part.type === Raw.ChatCompletionContentPartKind.Opaque) {
@@ -257,12 +226,24 @@ function extractThinkingData(content: Raw.ChatCompletionContentPart[]): OpenAI.R
257226
}));
258227
}
259228

260-
function extractPhaseData(content: Raw.ChatCompletionContentPart[]): IPhaseData | undefined {
229+
function extractPhaseData(content: Raw.ChatCompletionContentPart[]): string | undefined {
261230
for (const part of content) {
262231
if (part.type === Raw.ChatCompletionContentPartKind.Opaque) {
263232
const phaseData = rawPartAsPhaseData(part);
264233
if (phaseData) {
265-
return phaseData;
234+
return phaseData.phase;
235+
}
236+
}
237+
}
238+
return undefined;
239+
}
240+
241+
function extractResponseOutputMessageId(content: Raw.ChatCompletionContentPart[]): string | undefined {
242+
for (const part of content) {
243+
if (part.type === Raw.ChatCompletionContentPartKind.Opaque) {
244+
const id = rawPartAsResponseOutputMessageId(part);
245+
if (id) {
246+
return id;
266247
}
267248
}
268249
}
@@ -596,7 +577,7 @@ export class OpenAIResponsesProcessor {
596577
const phase = (chunk.item as ResponseOutputItemWithPhase).phase;
597578
onProgress({
598579
text: '',
599-
responseOutputMessageId: phase ? chunk.item.id : undefined,
580+
responseOutputMessageId: chunk.item.id,
600581
phase,
601582
});
602583
}

src/platform/endpoint/node/test/responsesApi.spec.ts

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import { describe, expect, it } from 'vitest';
99
import { TokenizerType } from '../../../../util/common/tokenizer';
1010
import { IInstantiationService } from '../../../../util/vs/platform/instantiation/common/instantiation';
1111
import { ILogService } from '../../../log/common/logService';
12+
import { IResponseDelta } from '../../../networking/common/fetch';
1213
import { IChatEndpoint, ICreateEndpointBodyOptions } from '../../../networking/common/networking';
1314
import { TelemetryData } from '../../../telemetry/common/telemetryData';
1415
import { SpyingTelemetryService } from '../../../telemetry/node/spyingTelemetryService';
1516
import { createFakeStreamResponse } from '../../../test/node/fetcher';
1617
import { createPlatformServices } from '../../../test/node/services';
1718
import { CustomDataPartMimeTypes } from '../../common/endpointTypes';
18-
import { createResponsesRequestBody, processResponseFromChatEndpoint, responseApiInputToRawMessagesForLogging } from '../responsesApi';
19+
import { createResponsesRequestBody, OpenAIResponsesProcessor, processResponseFromChatEndpoint, responseApiInputToRawMessagesForLogging } from '../responsesApi';
1920

2021
describe('responseApiInputToRawMessagesForLogging', () => {
2122

@@ -270,28 +271,33 @@ describe('createResponsesRequestBody', () => {
270271
return endpoint;
271272
};
272273

273-
it('only sends response output ids for phased assistant messages', () => {
274+
it('sends response output ids independently from phase', () => {
274275
const services = createPlatformServices();
275276
const accessor = services.createTestingAccessor();
276277
const endpoint = createTestEndpoint();
277278

278279
const nonPhaseAssistant = createAssistantMessage([
279280
{ type: Raw.ChatCompletionContentPartKind.Text, text: 'plain assistant reply' },
281+
{ type: Raw.ChatCompletionContentPartKind.Opaque, value: { type: CustomDataPartMimeTypes.ResponseOutputMessageId, responseOutputMessageId: 'msg_plain' } },
280282
]);
281283
const phaseAssistant = createAssistantMessage([
282284
{ type: Raw.ChatCompletionContentPartKind.Text, text: 'phase assistant reply' },
283-
{ type: Raw.ChatCompletionContentPartKind.Opaque, value: { type: CustomDataPartMimeTypes.PhaseData, phase: 'tool_calling', responseOutputMessageId: 'msg_phase' } },
285+
{ type: Raw.ChatCompletionContentPartKind.Opaque, value: { type: CustomDataPartMimeTypes.ResponseOutputMessageId, responseOutputMessageId: 'msg_phase' } },
286+
{ type: Raw.ChatCompletionContentPartKind.Opaque, value: { type: CustomDataPartMimeTypes.PhaseData, phase: 'tool_calling' } },
284287
]);
285288

286289
const body = createResponsesRequestBody(accessor, createRequestOptions([nonPhaseAssistant, phaseAssistant]), endpoint.model, endpoint);
287290
const input = body.input as OpenAI.Responses.ResponseInputItem[];
288291

289292
expect(input).toHaveLength(2);
290-
expect(input[0]).toEqual({
293+
expect(input[0]).toMatchObject({
291294
role: 'assistant',
292-
content: [{ type: 'input_text', text: 'plain assistant reply' }],
295+
content: [{ type: 'output_text', text: 'plain assistant reply', annotations: [] }],
296+
id: 'msg_plain',
297+
status: 'completed',
293298
type: 'message',
294299
});
300+
expect(input[0]).toHaveProperty('phase', undefined);
295301
expect(input[1]).toEqual({
296302
role: 'assistant',
297303
content: [{ type: 'output_text', text: 'phase assistant reply', annotations: [] }],
@@ -304,6 +310,34 @@ describe('createResponsesRequestBody', () => {
304310
accessor.dispose();
305311
services.dispose();
306312
});
313+
314+
it('streams response output ids even when no phase is present', async () => {
315+
const telemetryData = TelemetryData.createAndMarkAsIssued({ modelCallId: 'model-call-1' }, {});
316+
const deltas: IResponseDelta[] = [];
317+
const processor = new OpenAIResponsesProcessor(telemetryData, 'req_123', 'gh_123');
318+
319+
processor.push({
320+
type: 'response.output_item.done',
321+
sequence_number: 0,
322+
output_index: 0,
323+
item: {
324+
id: 'msg_plain',
325+
role: 'assistant',
326+
content: [{ type: 'output_text', text: 'plain assistant reply', annotations: [] }],
327+
status: 'completed',
328+
type: 'message',
329+
}
330+
} as OpenAI.Responses.ResponseOutputItemDoneEvent, async (_text, _index, delta: IResponseDelta) => {
331+
deltas.push(delta);
332+
return undefined;
333+
});
334+
335+
expect(deltas).toEqual([{
336+
text: '',
337+
responseOutputMessageId: 'msg_plain',
338+
phase: undefined,
339+
}]);
340+
});
307341
});
308342

309343
describe('processResponseFromChatEndpoint telemetry', () => {

0 commit comments

Comments
 (0)