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
@@ -56,4 +56,42 @@ describe('Task', () => {
expect(requests).toEqual(originalRequests);
});
describe('acceptAgentMessage', () => {
it('should set currentTraceId when event has traceId', async () => {
const mockConfig = createMockConfig();
const mockEventBus: ExecutionEventBus = {
publish: vi.fn(),
on: vi.fn(),
off: vi.fn(),
once: vi.fn(),
removeAllListeners: vi.fn(),
finished: vi.fn(),
};
// @ts-expect-error - Calling private constructor for test purposes.
const task = new Task(
'task-id',
'context-id',
mockConfig as Config,
mockEventBus,
);
const event = {
type: 'content',
value: 'test',
traceId: 'test-trace-id',
};
await task.acceptAgentMessage(event);
expect(mockEventBus.publish).toHaveBeenCalledWith(
expect.objectContaining({
metadata: expect.objectContaining({
traceId: 'test-trace-id',
}),
}),
);
});
});
});
+30 -5
View File
@@ -220,12 +220,14 @@ export class Task {
final = false,
timestamp?: string,
metadataError?: string,
traceId?: string,
): TaskStatusUpdateEvent {
const metadata: {
coderAgent: CoderAgentMessage;
model: string;
userTier?: UserTierId;
error?: string;
traceId?: string;
} = {
coderAgent: coderAgentMessage,
model: this.config.getModel(),
@@ -236,6 +238,10 @@ export class Task {
metadata.error = metadataError;
}
if (traceId) {
metadata.traceId = traceId;
}
return {
kind: 'status-update',
taskId: this.id,
@@ -257,6 +263,7 @@ export class Task {
messageParts?: Part[], // For more complex messages
final = false,
metadataError?: string,
traceId?: string,
): void {
this.taskState = newState;
let message: Message | undefined;
@@ -281,6 +288,7 @@ export class Task {
final,
undefined,
metadataError,
traceId,
);
this.eventBus?.publish(event);
}
@@ -582,10 +590,13 @@ export class Task {
const stateChange: StateChange = {
kind: CoderAgentEvent.StateChangeEvent,
};
const traceId =
'traceId' in event && event.traceId ? event.traceId : undefined;
switch (event.type) {
case GeminiEventType.Content:
logger.info('[Task] Sending agent message content...');
this._sendTextContent(event.value);
this._sendTextContent(event.value, traceId);
break;
case GeminiEventType.ToolCallRequest:
// This is now handled by the agent loop, which collects all requests
@@ -624,11 +635,13 @@ export class Task {
'Task cancelled by user',
undefined,
true,
undefined,
traceId,
);
break;
case GeminiEventType.Thought:
logger.info('[Task] Sending agent thought...');
this._sendThought(event.value);
this._sendThought(event.value, traceId);
break;
case GeminiEventType.ChatCompressed:
break;
@@ -658,6 +671,7 @@ export class Task {
undefined,
false,
errMessage,
traceId,
);
break;
}
@@ -915,7 +929,7 @@ export class Task {
}
}
_sendTextContent(content: string): void {
_sendTextContent(content: string, traceId?: string): void {
if (content === '') {
return;
}
@@ -930,11 +944,14 @@ export class Task {
textContent,
message,
false,
undefined,
undefined,
traceId,
),
);
}
_sendThought(content: ThoughtSummary): void {
_sendThought(content: ThoughtSummary, traceId?: string): void {
if (!content.subject && !content.description) {
return;
}
@@ -956,7 +973,15 @@ export class Task {
kind: CoderAgentEvent.ThoughtEvent,
};
this.eventBus?.publish(
this._createStatusUpdateEvent(this.taskState, thought, message, false),
this._createStatusUpdateEvent(
this.taskState,
thought,
message,
false,
undefined,
undefined,
traceId,
),
);
}
}
+28
View File
@@ -624,4 +624,32 @@ describe('E2E Tests', () => {
assertUniqueFinalEventIsLast(events);
expect(events.length).toBe(10);
});
it('should include traceId in status updates when available', async () => {
const traceId = 'test-trace-id';
sendMessageStreamSpy.mockImplementation(async function* () {
yield* [
{ type: 'content', value: 'Hello', traceId },
{ type: 'thought', value: { subject: 'Thinking...' }, traceId },
];
});
const agent = request.agent(app);
const res = await agent
.post('/')
.send(createStreamMessageRequest('hello', 'a2a-trace-id-test'))
.set('Content-Type', 'application/json')
.expect(200);
const events = streamToSSEEvents(res.text);
// The first two events are task-creation and working status
const textContentEvent = events[2].result as TaskStatusUpdateEvent;
expect(textContentEvent.kind).toBe('status-update');
expect(textContentEvent.metadata?.['traceId']).toBe(traceId);
const thoughtEvent = events[3].result as TaskStatusUpdateEvent;
expect(thoughtEvent.kind).toBe('status-update');
expect(thoughtEvent.metadata?.['traceId']).toBe(traceId);
});
});