Skip to content

Conversation

@rainbow-f1zz
Copy link

Proposal

Introduce a new parameter streamAgentTools in StreamRunOptions. When set to true, the StreamRunResult object returned by Runner.run() contains all stream events from current agent, and all agents invoked via agent-as-tools, recursively.

For example, the following setup:

const agentB = new Agent(...);
const agentA = new Agent(
  ..., 
  tools=[agentB.asTool(...)]
);
const runner = new Runner(...);
const result = runner.run(
  agentA,
  'Solve task by using agentB as a tool',
  {
    stream: true,
    streamAgentTools: true,
  }
);

We can consume events by for await (const event of result) {...}, and events originate from both agent A and agent B.

Background

I was working on an app with a main agent using other tool agents as tools, for better responsibility separation. To provide live update to end users, I process stream events from the main agent. In current implementation, the events from main stream only consist of tool_called / tool_output in run item events, or corresponding raw events.

This behavior creates a challenge for live updates - end users can see that the main agent is invoking a tool, but it may look stuck if tool agent takes a while to run. Although there are workarounds like writing updates in agent hooks, they all require code changes to handle this case. What I needed was a straightforward toggle to enable stream events from all agents recursively, working with agent-as-tools, handoffs, etc.

I see similar PRs requesting for features in the Python library, so I feel that this is a pretty common use case.

Implementation details

I added a context-scope stream that all runners invoked with a single RunContext can use to send stream events. The first runner owns the stream, and other runner (initialized indirectly inside asTool()) copies events to the context-scope stream if necessary.

Changes

  1. added streamAgentTools in StreamRunOptions
  2. updated StreamRunResult to handle streamAgentTools; created context-scope stream; copied events to the context-scope stream; handled completion/abort/failure scenarios
  3. updated asTool execute method to check run context; if enabled, passed StreamRunOptions to runner and await for completion
  4. added a stream-agent-tools.ts example snippet
  5. unit tests

Unrelated fix

This PR also contains 2 commits to (1) remove redundant tool name replacement (non-alphanumeric -> underscore already handles space -> underscore), and (2) fix a typo (Schame -> Schema).

@changeset-bot
Copy link

changeset-bot bot commented Nov 25, 2025

⚠️ No Changeset found

Latest commit: 34d6cce

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 287 to 291
this.#contextScopeStreamOwner = true;
this.state._context._copyToContextScopeStream = true;
},
cancel: () => {},
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate cancellation for context scope stream

When streamAgentTools is enabled, the async iterator returned from StreamedRunResult is the context-scope stream created here, but its cancel handler is a no-op and never marks the result as cancelled. If a caller stops consuming the stream (e.g., breaks out of for await), the context stream is cancelled but #cancelled stays false, so _addItem continues to enqueue into a cancelled controller and the run loop keeps executing nested agents instead of short-circuiting. This regresses the previous cancellation semantics (line 264) and can throw once the controller is closed or waste work for abandoned streams. The cancel handler should set the same cancellation flag/cleanup as the primary stream.

Useful? React with 👍 / 👎.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants