feat: Propagate traceId from code assist to response metadata (Fixes … (#11360)

Co-authored-by: owenofbrien <86964623+owenofbrien@users.noreply.github.com>
This commit is contained in:
Paweł Dec
2025-10-20 22:00:24 +02:00
committed by GitHub
parent 085e5b1f4d
commit 36de686224
7 changed files with 187 additions and 6 deletions
@@ -310,6 +310,27 @@ describe('converter', () => {
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.modelVersion).toEqual('gemini-2.5-pro');
});
it('should handle traceId', () => {
const codeAssistRes: CaGenerateContentResponse = {
response: {
candidates: [],
},
traceId: 'my-trace-id',
};
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.responseId).toEqual('my-trace-id');
});
it('should handle missing traceId', () => {
const codeAssistRes: CaGenerateContentResponse = {
response: {
candidates: [],
},
};
const genaiRes = fromGenerateContentResponse(codeAssistRes);
expect(genaiRes.responseId).toBeUndefined();
});
});
describe('toContents', () => {
@@ -73,6 +73,7 @@ interface VertexGenerationConfig {
export interface CaGenerateContentResponse {
response: VertexGenerateContentResponse;
traceId?: string;
}
interface VertexGenerateContentResponse {
@@ -139,6 +140,7 @@ export function fromGenerateContentResponse(
out.promptFeedback = inres.promptFeedback;
out.usageMetadata = inres.usageMetadata;
out.modelVersion = inres.modelVersion;
out.responseId = res.traceId;
return out;
}
+62
View File
@@ -784,6 +784,68 @@ describe('Turn', () => {
{ type: GeminiEventType.Content, value: 'Success' },
]);
});
it('should yield content events with traceId', async () => {
const mockResponseStream = (async function* () {
yield {
type: StreamEventType.CHUNK,
value: {
candidates: [{ content: { parts: [{ text: 'Hello' }] } }],
responseId: 'trace-123',
} as GenerateContentResponse,
};
})();
mockSendMessageStream.mockResolvedValue(mockResponseStream);
const events = [];
for await (const event of turn.run(
'test-model',
[{ text: 'Hi' }],
new AbortController().signal,
)) {
events.push(event);
}
expect(events).toEqual([
{ type: GeminiEventType.Content, value: 'Hello', traceId: 'trace-123' },
]);
});
it('should yield thought events with traceId', async () => {
const mockResponseStream = (async function* () {
yield {
type: StreamEventType.CHUNK,
value: {
candidates: [
{
content: {
parts: [{ text: '[Thought: thinking]', thought: 'thinking' }],
},
},
],
responseId: 'trace-456',
} as unknown as GenerateContentResponse,
};
})();
mockSendMessageStream.mockResolvedValue(mockResponseStream);
const events = [];
for await (const event of turn.run(
'test-model',
[{ text: 'Hi' }],
new AbortController().signal,
)) {
events.push(event);
}
expect(events).toEqual([
{
type: GeminiEventType.Thought,
value: { subject: '', description: '[Thought: thinking]' },
traceId: 'trace-456',
},
]);
});
});
describe('getDebugResponses', () => {
+6 -1
View File
@@ -120,11 +120,13 @@ export interface ServerToolCallConfirmationDetails {
export type ServerGeminiContentEvent = {
type: GeminiEventType.Content;
value: string;
traceId?: string;
};
export type ServerGeminiThoughtEvent = {
type: GeminiEventType.Thought;
value: ThoughtSummary;
traceId?: string;
};
export type ServerGeminiToolCallRequestEvent = {
@@ -261,19 +263,22 @@ export class Turn {
this.debugResponses.push(resp);
const traceId = resp.responseId;
const thoughtPart = resp.candidates?.[0]?.content?.parts?.[0];
if (thoughtPart?.thought) {
const thought = parseThought(thoughtPart.text ?? '');
yield {
type: GeminiEventType.Thought,
value: thought,
traceId,
};
continue;
}
const text = getResponseText(resp);
if (text) {
yield { type: GeminiEventType.Content, value: text };
yield { type: GeminiEventType.Content, value: text, traceId };
}
// Handle function calls (requesting tool execution)