Skip to content

Commit d139bce

Browse files
committed
refactor(core): isolate specific agent changes
1 parent 4ad7b5a commit d139bce

7 files changed

Lines changed: 114 additions & 77 deletions

File tree

packages/core/src/agent/content-utils.test.ts

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import { describe, expect, it } from 'vitest';
88
import {
99
geminiPartsToContentParts,
1010
contentPartsToGeminiParts,
11-
toolResultDisplayToContentParts,
1211
buildToolResponseData,
1312
} from './content-utils.js';
1413
import type { Part } from '@google/genai';
@@ -200,27 +199,6 @@ describe('contentPartsToGeminiParts', () => {
200199
});
201200
});
202201

203-
describe('toolResultDisplayToContentParts', () => {
204-
it('returns undefined for undefined', () => {
205-
expect(toolResultDisplayToContentParts(undefined)).toBeUndefined();
206-
});
207-
208-
it('returns undefined for null', () => {
209-
expect(toolResultDisplayToContentParts(null)).toBeUndefined();
210-
});
211-
212-
it('handles string resultDisplay as-is', () => {
213-
const result = toolResultDisplayToContentParts('File written');
214-
expect(result).toEqual([{ type: 'text', text: 'File written' }]);
215-
});
216-
217-
it('stringifies object resultDisplay', () => {
218-
const display = { type: 'FileDiff', oldPath: 'a.ts', newPath: 'b.ts' };
219-
const result = toolResultDisplayToContentParts(display);
220-
expect(result).toEqual([{ type: 'text', text: JSON.stringify(display) }]);
221-
});
222-
});
223-
224202
describe('buildToolResponseData', () => {
225203
it('preserves outputFile and contentLength', () => {
226204
const result = buildToolResponseData({

packages/core/src/agent/content-utils.ts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,6 @@ export function contentPartsToGeminiParts(content: ContentPart[]): Part[] {
105105
return result;
106106
}
107107

108-
/**
109-
* Converts a ToolCallResponseInfo.resultDisplay value into ContentPart[].
110-
* Handles string, object-valued (FileDiff, SubagentProgress, etc.),
111-
* and undefined resultDisplay consistently.
112-
*/
113-
export function toolResultDisplayToContentParts(
114-
resultDisplay: unknown,
115-
): ContentPart[] | undefined {
116-
if (resultDisplay === undefined || resultDisplay === null) {
117-
return undefined;
118-
}
119-
const text =
120-
typeof resultDisplay === 'string'
121-
? resultDisplay
122-
: JSON.stringify(resultDisplay);
123-
return [{ type: 'text', text }];
124-
}
125-
126108
/**
127109
* Builds the data record for a tool_response AgentEvent, preserving
128110
* all available metadata from the ToolCallResponseInfo.

packages/core/src/agent/event-translator.test.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -155,9 +155,10 @@ describe('translateEvent', () => {
155155
expect(resp.content).toEqual([
156156
{ type: 'text', text: 'Permission denied to write' },
157157
]);
158-
expect(resp.displayContent).toEqual([
159-
{ type: 'text', text: 'Permission denied' },
160-
]);
158+
expect(resp.display?.result).toEqual({
159+
type: 'text',
160+
text: 'Permission denied',
161+
});
161162
expect(resp.data).toEqual({ errorType: 'permission_denied' });
162163
});
163164

@@ -200,9 +201,12 @@ describe('translateEvent', () => {
200201
};
201202
const result = translateEvent(event, state);
202203
const resp = result[0] as AgentEvent<'tool_response'>;
203-
expect(resp.displayContent).toEqual([
204-
{ type: 'text', text: JSON.stringify(objectDisplay) },
205-
]);
204+
expect(resp.display?.result).toEqual({
205+
type: 'diff',
206+
path: '/tmp/test.txt',
207+
beforeText: 'a',
208+
afterText: 'b',
209+
});
206210
});
207211

208212
it('passes through string resultDisplay as-is', () => {
@@ -220,9 +224,10 @@ describe('translateEvent', () => {
220224
};
221225
const result = translateEvent(event, state);
222226
const resp = result[0] as AgentEvent<'tool_response'>;
223-
expect(resp.displayContent).toEqual([
224-
{ type: 'text', text: 'Command output text' },
225-
]);
227+
expect(resp.display?.result).toEqual({
228+
type: 'text',
229+
text: 'Command output text',
230+
});
226231
});
227232

228233
it('preserves outputFile and contentLength in data', () => {
@@ -373,7 +378,7 @@ describe('translateEvent', () => {
373378
expect(err.type).toBe('error');
374379
expect(err.fatal).toBe(false);
375380
expect(err._meta?.['code']).toBe('AGENT_EXECUTION_BLOCKED');
376-
expect(err.message).toBe('Agent execution blocked: Policy violation');
381+
expect(err.message).toBe('Policy violation');
377382
});
378383

379384
it('uses systemMessage in the final error message when available', () => {
@@ -388,9 +393,7 @@ describe('translateEvent', () => {
388393
};
389394
const result = translateEvent(event, state);
390395
const err = result[0] as AgentEvent<'error'>;
391-
expect(err.message).toBe(
392-
'Agent execution blocked: Blocked by policy hook',
393-
);
396+
expect(err.message).toBe('Blocked by policy hook');
394397
});
395398
});
396399

packages/core/src/agent/event-translator.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,13 @@ import type {
2525
ErrorData,
2626
Usage,
2727
AgentEventType,
28+
ToolDisplay,
2829
} from './types.js';
2930
import {
3031
geminiPartsToContentParts,
31-
toolResultDisplayToContentParts,
3232
buildToolResponseData,
3333
} from './content-utils.js';
34+
import { toolResultDisplayToDisplayContent } from './tool-display-utils.js';
3435

3536
// ---------------------------------------------------------------------------
3637
// Translation State
@@ -209,7 +210,7 @@ export function translateEvent(
209210
out.push(
210211
makeEvent('error', state, {
211212
status: 'PERMISSION_DENIED',
212-
message: `Agent execution blocked: ${event.value.systemMessage?.trim() || event.value.reason}`,
213+
message: event.value.systemMessage?.trim() || event.value.reason,
213214
fatal: false,
214215
_meta: { code: 'AGENT_EXECUTION_BLOCKED' },
215216
}),
@@ -235,16 +236,23 @@ export function translateEvent(
235236
requestId: event.value.callId,
236237
name: event.value.name,
237238
args: event.value.args,
239+
display: event.value.display,
238240
}),
239241
);
240242
break;
241243

242244
case GeminiEventType.ToolCallResponse: {
243245
ensureStreamStart(state, out);
244-
const displayContent = toolResultDisplayToContentParts(
245-
event.value.resultDisplay,
246-
);
247246
const data = buildToolResponseData(event.value);
247+
const display: ToolDisplay | undefined =
248+
event.value.display ??
249+
(event.value.resultDisplay
250+
? {
251+
result: toolResultDisplayToDisplayContent(
252+
event.value.resultDisplay,
253+
),
254+
}
255+
: undefined);
248256
out.push(
249257
makeEvent('tool_response', state, {
250258
requestId: event.value.callId,
@@ -253,7 +261,7 @@ export function translateEvent(
253261
? [{ type: 'text', text: event.value.error.message }]
254262
: geminiPartsToContentParts(event.value.responseParts),
255263
isError: event.value.error !== undefined,
256-
...(displayContent ? { displayContent } : {}),
264+
...(display ? { display } : {}),
257265
...(data ? { data } : {}),
258266
}),
259267
);
@@ -274,7 +282,6 @@ export function translateEvent(
274282
((x: never) => {
275283
throw new Error(`Unhandled event type: ${JSON.stringify(x)}`);
276284
})(event);
277-
break;
278285
}
279286

280287
return out;

packages/core/src/agent/legacy-agent-session.test.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,10 @@ function makeCompletedToolCall(
102102
response: {
103103
callId,
104104
responseParts: [{ text: responseText }],
105-
resultDisplay: undefined,
105+
resultDisplay: responseText,
106+
display: {
107+
result: { type: 'text', text: responseText },
108+
},
106109
error: undefined,
107110
errorType: undefined,
108111
},
@@ -200,7 +203,6 @@ describe('LegacyAgentSession', () => {
200203
expect.any(AbortSignal),
201204
'test-prompt',
202205
undefined,
203-
false,
204206
'raw input',
205207
);
206208

@@ -427,6 +429,12 @@ describe('LegacyAgentSession', () => {
427429
(e): e is AgentEvent<'tool_response'> => e.type === 'tool_response',
428430
);
429431
expect(toolResp?.name).toBe('read_file');
432+
expect(toolResp?.display).toEqual(
433+
expect.objectContaining({
434+
name: 'read_file',
435+
result: { type: 'text', text: 'file contents' },
436+
}),
437+
);
430438
expect(toolResp?.content).toEqual([
431439
{ type: 'text', text: 'file contents' },
432440
]);
@@ -489,9 +497,10 @@ describe('LegacyAgentSession', () => {
489497
expect(toolResp?.content).toEqual([
490498
{ type: 'text', text: 'Permission denied' },
491499
]);
492-
expect(toolResp?.displayContent).toEqual([
493-
{ type: 'text', text: 'Error display' },
494-
]);
500+
expect(toolResp?.display?.result).toEqual({
501+
type: 'text',
502+
text: 'Error display',
503+
});
495504
});
496505

497506
it('stops on STOP_EXECUTION tool error', async () => {
@@ -646,7 +655,7 @@ describe('LegacyAgentSession', () => {
646655
e.type === 'error' && e._meta?.['code'] === 'AGENT_EXECUTION_BLOCKED',
647656
);
648657
expect(blocked?.fatal).toBe(false);
649-
expect(blocked?.message).toBe('Agent execution blocked: Blocked by hook');
658+
expect(blocked?.message).toBe('Blocked by hook');
650659

651660
const messages = events.filter(
652661
(e): e is AgentEvent<'message'> =>

packages/core/src/agent/legacy-agent-session.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ import {
2323
buildToolResponseData,
2424
contentPartsToGeminiParts,
2525
geminiPartsToContentParts,
26-
toolResultDisplayToContentParts,
2726
} from './content-utils.js';
27+
import { populateToolDisplay } from './tool-display-utils.js';
2828
import { AgentSession } from './agent-session.js';
2929
import {
3030
createTranslationState,
@@ -197,7 +197,6 @@ export class LegacyAgentProtocol implements AgentProtocol {
197197
this._abortController.signal,
198198
this._promptId,
199199
undefined,
200-
false,
201200
currentDisplayContent,
202201
);
203202
currentDisplayContent = undefined;
@@ -263,9 +262,13 @@ export class LegacyAgentProtocol implements AgentProtocol {
263262
const content: ContentPart[] = response.error
264263
? [{ type: 'text', text: response.error.message }]
265264
: geminiPartsToContentParts(response.responseParts);
266-
const displayContent = toolResultDisplayToContentParts(
267-
response.resultDisplay,
268-
);
265+
const display = populateToolDisplay({
266+
name: request.name,
267+
invocation: 'invocation' in tc ? tc.invocation : undefined,
268+
resultDisplay: response.resultDisplay,
269+
displayName: 'tool' in tc ? tc.tool?.displayName : undefined,
270+
display: response.display,
271+
});
269272
const data = buildToolResponseData(response);
270273

271274
this._emit([
@@ -274,7 +277,7 @@ export class LegacyAgentProtocol implements AgentProtocol {
274277
name: request.name,
275278
content,
276279
isError: response.error !== undefined,
277-
...(displayContent ? { displayContent } : {}),
280+
...(display ? { display } : {}),
278281
...(data ? { data } : {}),
279282
}),
280283
]);

0 commit comments

Comments
 (0)