diff --git a/.changeset/two-terms-walk.md b/.changeset/two-terms-walk.md new file mode 100644 index 00000000..076dbbdc --- /dev/null +++ b/.changeset/two-terms-walk.md @@ -0,0 +1,5 @@ +--- +'@openai/agents-core': patch +--- + +fix: event data adjustment for #749 diff --git a/examples/agent-patterns/agents-as-tools-streaming.ts b/examples/agent-patterns/agents-as-tools-streaming.ts index 9943d798..b010acbe 100644 --- a/examples/agent-patterns/agents-as-tools-streaming.ts +++ b/examples/agent-patterns/agents-as-tools-streaming.ts @@ -36,7 +36,7 @@ async function main() { // When you pass onStream handler, the agent is executed in streaming mode. onStream: (event) => { console.log( - `### onStream method streaming event from ${event.agentName} in streaming mode:\n\n` + + `### onStream method streaming event from ${event.agent.name} in streaming mode:\n\n` + JSON.stringify(event) + '\n', ); @@ -47,7 +47,7 @@ async function main() { /* billingAgentTool.on('raw_model_stream_event', (event) => { console.log( - `### on method streaming event from ${event.agentName} in streaming mode:\n\n` + + `### on method streaming event from ${event.agent.name} in streaming mode:\n\n` + JSON.stringify(event) + '\n', ); diff --git a/packages/agents-core/src/agent.ts b/packages/agents-core/src/agent.ts index 59321aaa..04615ab5 100644 --- a/packages/agents-core/src/agent.ts +++ b/packages/agents-core/src/agent.ts @@ -51,25 +51,26 @@ type CompletedRunResult> = ( type AgentToolRunOptions = Omit, 'stream'>; -type AgentToolStreamEvent = { +type AgentToolStreamEvent> = { // Raw stream event emitted by the nested agent run. event: RunStreamEvent; - // Convenience metadata so callers can correlate to the invoking tool call/agent. - agentName: string; - toolCallId?: string; + // The agent instance being executed as a tool. + agent: TAgent; + // The tool call item that triggered this nested run (when available). + toolCall?: protocol.FunctionCallItem; }; -type AgentToolEventName = AgentToolStreamEvent['event']['type'] | '*'; -type AgentToolEventHandler = ( - event: AgentToolStreamEvent, +type AgentToolEventName = RunStreamEvent['type'] | '*'; +type AgentToolEventHandler> = ( + event: AgentToolStreamEvent, ) => void | Promise; -type AgentTool = FunctionTool< +type AgentTool> = FunctionTool< TContext, typeof AgentAsToolNeedApprovalSchame > & { on: ( name: AgentToolEventName, - handler: AgentToolEventHandler, - ) => AgentTool; + handler: AgentToolEventHandler, + ) => AgentTool; }; // Per-process, ephemeral map linking a function tool call to its nested @@ -566,9 +567,9 @@ export class Agent< /** * Optional hook to receive streamed events from the nested agent run. */ - onStream?: (event: AgentToolStreamEvent) => void | Promise; + onStream?: (event: AgentToolStreamEvent) => void | Promise; }, - ): AgentTool { + ): AgentTool { const { toolName, toolDescription, @@ -582,9 +583,9 @@ export class Agent< // Event handlers are scoped to this agent tool instance and are not shared; we only support registration (no removal) to keep the API surface small. const eventHandlers = new Map< AgentToolEventName, - Set + Set> >(); - const emitEvent = async (event: AgentToolStreamEvent) => { + const emitEvent = async (event: AgentToolStreamEvent) => { // We intentionally keep only add semantics (no off) to reduce surface area; handlers are scoped to this agent tool instance. const specific = eventHandlers.get(event.event.type); const wildcard = eventHandlers.get('*'); @@ -627,9 +628,8 @@ export class Agent< ...(runOptions ?? {}), }); const streamPayload = { - agentName: this.name, - // Tool calls should carry IDs, but direct invocation or provider quirks can omit it, so keep this optional. - toolCallId: details?.toolCall?.callId, + agent: this, + toolCall: details?.toolCall, }; if (shouldStream) { @@ -682,10 +682,11 @@ export class Agent< }, }); - const agentTool: AgentTool = { + const agentTool: AgentTool = { ...baseTool, on: (name, handler) => { - const set = eventHandlers.get(name) ?? new Set(); + const set = + eventHandlers.get(name) ?? new Set>(); set.add(handler); eventHandlers.set(name, set); return agentTool; diff --git a/packages/agents-core/test/agent.test.ts b/packages/agents-core/test/agent.test.ts index b78ce254..9c2ae71e 100644 --- a/packages/agents-core/test/agent.test.ts +++ b/packages/agents-core/test/agent.test.ts @@ -344,18 +344,21 @@ describe('Agent', () => { expect.objectContaining({ stream: true }), ); expect(onStream).toHaveBeenCalledTimes(streamEvents.length); - expect(onStream).toHaveBeenCalledWith({ - event: streamEvents[0], - agentName: agent.name, - toolCallId: undefined, - }); + expect(onStream).toHaveBeenCalledWith( + expect.objectContaining({ + event: streamEvents[0], + agent, + toolCall: undefined, + }), + ); }); - it('includes toolCallId and agentName when streaming from nested agent tools', async () => { + it('includes toolCall when streaming from nested agent tools', async () => { const agent = new Agent({ name: 'Streamer Agent', instructions: 'Stream things.', }); + const toolCall = { callId: 'call-123' } as any; const streamEvents = [ { type: 'raw_model_stream_event', data: { type: 'response_started' } }, ] as any[]; @@ -392,7 +395,7 @@ describe('Agent', () => { const output = await tool.invoke( new RunContext(), '{"input":"run streaming"}', - { toolCall: { callId: 'call-123' } as any }, + { toolCall }, ); expect(output).toBe('tool output'); @@ -401,11 +404,13 @@ describe('Agent', () => { 'run streaming', expect.objectContaining({ stream: true }), ); - expect(onStream).toHaveBeenCalledWith({ - agentName: 'Streamer Agent', - event: streamEvents[0], - toolCallId: 'call-123', - }); + expect(onStream).toHaveBeenCalledWith( + expect.objectContaining({ + agent, + toolCall, + event: streamEvents[0], + }), + ); }); it('supports event handlers registered via on (agent tool only) and wildcard handlers', async () => { @@ -453,10 +458,11 @@ describe('Agent', () => { tool.on('raw_model_stream_event', rawHandler).on('*', wildcardHandler); + const toolCall = { callId: 'call-abc' } as any; const output = await tool.invoke( new RunContext(), '{"input":"run streaming"}', - { toolCall: { callId: 'call-abc' } as any }, + { toolCall }, ); expect(output).toBe('tool output'); @@ -466,11 +472,13 @@ describe('Agent', () => { expect.objectContaining({ stream: true }), ); expect(rawHandler).toHaveBeenCalledTimes(1); - expect(rawHandler).toHaveBeenCalledWith({ - agentName: 'Streamer Agent', - event: streamEvents[0], - toolCallId: 'call-abc', - }); + expect(rawHandler).toHaveBeenCalledWith( + expect.objectContaining({ + agent, + toolCall, + event: streamEvents[0], + }), + ); expect(wildcardHandler).toHaveBeenCalledTimes(streamEvents.length); }); @@ -513,10 +521,11 @@ describe('Agent', () => { tool.on('raw_model_stream_event', handler); + const toolCall = { callId: 'call-xyz' } as any; const output = await tool.invoke( new RunContext(), '{"input":"run streaming"}', - { toolCall: { callId: 'call-xyz' } as any }, + { toolCall }, ); expect(output).toBe('tool output'); @@ -525,11 +534,13 @@ describe('Agent', () => { 'run streaming', expect.objectContaining({ stream: true }), ); - expect(handler).toHaveBeenCalledWith({ - agentName: 'Handler Agent', - event: streamEvents[0], - toolCallId: 'call-xyz', - }); + expect(handler).toHaveBeenCalledWith( + expect.objectContaining({ + agent, + toolCall, + event: streamEvents[0], + }), + ); }); it('filters tools using isEnabled predicates', async () => {