Skip to content

Commit 8bfeeda

Browse files
committed
fix review comment
1 parent 60f7598 commit 8bfeeda

File tree

2 files changed

+47
-3
lines changed

2 files changed

+47
-3
lines changed

packages/agents-core/src/run.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,13 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
427427
inputItems: AgentInputItem[],
428428
systemInstructions: string | undefined,
429429
): Promise<ModelInputData> {
430-
// Always create a shallow copy so downstream mutations inside filters cannot affect
430+
const cloneInputItems = (items: AgentInputItem[]) =>
431+
items.map((item) => structuredClone(item));
432+
433+
// Always create a deep copy so downstream mutations inside filters cannot affect
431434
// the cached turn state.
432435
const base: ModelInputData = {
433-
input: [...inputItems],
436+
input: cloneInputItems(inputItems),
434437
instructions: systemInstructions,
435438
};
436439

@@ -452,7 +455,7 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
452455
}
453456

454457
return {
455-
input: [...result.input],
458+
input: cloneInputItems(result.input),
456459
instructions:
457460
typeof result.instructions === 'undefined'
458461
? systemInstructions

packages/agents-core/test/run.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1042,6 +1042,47 @@ describe('Runner.run', () => {
10421042
expect(getFirstTextContent(streamInput[0])).toBe('Alpha');
10431043
});
10441044

1045+
it('does not mutate run history when filter mutates input items', async () => {
1046+
const model = new FilterTrackingModel([
1047+
{
1048+
...TEST_MODEL_RESPONSE_BASIC,
1049+
},
1050+
]);
1051+
const agent = new Agent({
1052+
name: 'HistoryFilterAgent',
1053+
model,
1054+
});
1055+
1056+
const originalText = 'Top secret message';
1057+
const redactedText = '[redacted]';
1058+
1059+
const runner = new Runner({
1060+
callModelInputFilter: ({ modelData }) => {
1061+
const first = modelData.input[0];
1062+
if (
1063+
first?.type === 'message' &&
1064+
Array.isArray(first.content) &&
1065+
first.content.length > 0
1066+
) {
1067+
const firstChunk = first.content[0] as { text?: string };
1068+
if (firstChunk) {
1069+
firstChunk.text = redactedText;
1070+
}
1071+
}
1072+
return modelData;
1073+
},
1074+
});
1075+
1076+
const result = await runner.run(agent, [user(originalText)]);
1077+
1078+
const sentInput = model.lastRequest?.input as AgentInputItem[];
1079+
expect(Array.isArray(sentInput)).toBe(true);
1080+
expect(getFirstTextContent(sentInput[0])).toBe(redactedText);
1081+
1082+
const history = result.history;
1083+
expect(getFirstTextContent(history[0])).toBe(originalText);
1084+
});
1085+
10451086
it('throws when filter returns invalid data', async () => {
10461087
const model = new FilterTrackingModel([
10471088
{

0 commit comments

Comments
 (0)