Skip to content

Commit ed66acf

Browse files
authored
fix(core): emit correct agent in agent_updated_stream_event during streaming handoff (#205)
1 parent 72fca4b commit ed66acf

File tree

3 files changed

+81
-2
lines changed

3 files changed

+81
-2
lines changed

.changeset/twelve-poets-mate.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@openai/agents-core': patch
3+
---
4+
5+
Fixes handling of `agent_updated_stream_event` in run implementation and adds corresponding test coverage.

packages/agents-core/src/run.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,9 @@ export class Runner extends RunHooks<any, AgentOutputType<unknown>> {
824824
resetCurrentSpan();
825825
}
826826
result.state._currentAgentSpan = undefined;
827-
result._addItem(new RunAgentUpdatedStreamEvent(currentAgent));
827+
result._addItem(
828+
new RunAgentUpdatedStreamEvent(result.state._currentAgent),
829+
);
828830
result.state._noActiveAgentRun = true;
829831

830832
// we've processed the handoff, so we need to run the loop again

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

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,17 @@ import {
44
run,
55
setDefaultModelProvider,
66
setTracingDisabled,
7+
Usage,
8+
RunStreamEvent,
9+
RunAgentUpdatedStreamEvent,
10+
handoff,
11+
Model,
12+
ModelRequest,
13+
ModelResponse,
14+
StreamEvent,
15+
FunctionCallItem,
716
} from '../src';
8-
import { FakeModel, FakeModelProvider } from './stubs';
17+
import { FakeModel, FakeModelProvider, fakeModelMessage } from './stubs';
918

1019
// Test for unhandled rejection when stream loop throws
1120

@@ -43,4 +52,67 @@ describe('Runner.run (streaming)', () => {
4352

4453
expect((result.error as Error).message).toBe('Not implemented');
4554
});
55+
56+
it('emits agent_updated_stream_event with new agent on handoff', async () => {
57+
class SimpleStreamingModel implements Model {
58+
constructor(private resp: ModelResponse) {}
59+
async getResponse(_req: ModelRequest): Promise<ModelResponse> {
60+
return this.resp;
61+
}
62+
async *getStreamedResponse(): AsyncIterable<StreamEvent> {
63+
yield {
64+
type: 'response_done',
65+
response: {
66+
id: 'r',
67+
usage: {
68+
requests: 1,
69+
inputTokens: 0,
70+
outputTokens: 0,
71+
totalTokens: 0,
72+
},
73+
output: this.resp.output,
74+
},
75+
} as any;
76+
}
77+
}
78+
79+
const agentB = new Agent({
80+
name: 'B',
81+
model: new SimpleStreamingModel({
82+
output: [fakeModelMessage('done B')],
83+
usage: new Usage(),
84+
}),
85+
});
86+
87+
const callItem: FunctionCallItem = {
88+
id: 'h1',
89+
type: 'function_call',
90+
name: handoff(agentB).toolName,
91+
callId: 'c1',
92+
status: 'completed',
93+
arguments: '{}',
94+
};
95+
96+
const agentA = new Agent({
97+
name: 'A',
98+
model: new SimpleStreamingModel({
99+
output: [callItem],
100+
usage: new Usage(),
101+
}),
102+
handoffs: [handoff(agentB)],
103+
});
104+
105+
const result = await run(agentA, 'hi', { stream: true });
106+
const events: RunStreamEvent[] = [];
107+
for await (const e of result.toStream()) {
108+
events.push(e);
109+
}
110+
await result.completed;
111+
112+
const update = events.find(
113+
(e): e is RunAgentUpdatedStreamEvent =>
114+
e.type === 'agent_updated_stream_event',
115+
);
116+
expect(update?.agent).toBe(agentB);
117+
});
46118
});

0 commit comments

Comments
 (0)