Reviewed PR description, comments, commits, and changed files.
UnderstandIdentified linked issues and reviewed their content.
UnderstandAnalyzed changelog entries for formatting issues.
UnderstandNoted duplicate 'Added' sections and missing attribution in changelogs.
EditYou are given one or more GitHub PR URLs: https://github.com/badlogic/pi-mono/pull/1105
For each PR URL, do the following in order:
packages/*/CHANGELOG.md files. Report whether an entry exists. If missing, state that a changelog entry is required before merge and that you will add it if the user decides to merge. Follow the changelog format rules in AGENTS.md. Verify:
### Breaking Changes, ### Added, ### Fixed, etc.)Fixed foo ([#123](https://github.com/badlogic/pi-mono/pull/123) by [@user](https://github.com/user))### Breaking Changes, not just ### FixedOutput format per PR: PR: Changelog:
If no issues are found, say so under Bad and Ugly.
Planning PR inspection workflow
I'm preparing to inspect the PR fully by retrieving its title, body, comments, commits, and changed files using gh pr view --json plus diff data, while ensuring to read full base files properly with the designated read tool rather than using sed or cat.
>gh pr view 1105 --json title,body,comments,commits,files{"body":"This PR lets custom providers registered via `streamSimple` emit tool execution events during streaming, so the TUI shows full results just like built-in tool calls.\r\n\r\nPreviously, `AssistantMessageEventStream` only carried LLM-level events. Providers that use bidirectional protocols where the server requests tool execution mid-stream and blocks until results come back had no way to surface what happened. Certain custom provider integrations that shall remain unmentioned would show tool names and args but never the output.\r\n\r\nThe agent loop forwards these to the existing `tool_execution_*` events the TUI already handles.\r\n\r\n```typescript\r\nstream.push({\r\n type: \"tool_exec_start\",\r\n toolCallId: \"exec-1\",\r\n toolName: \"read\",\r\n args: { path: \"src/index.ts\" },\r\n});\r\n\r\n// ... provider executes tool on behalf of remote server ...\r\n\r\nstream.push({\r\n type: \"tool_exec_end\",\r\n toolCallId: \"exec-1\",\r\n toolName: \"read\",\r\n result: {\r\n content: [{ type: \"text\", text: fileContents }],\r\n details: { totalLines: 42 },\r\n },\r\n isError: false,\r\n});\r\n```\r\n","comments":[{"id":"IC_kwDOPbFNk87kOv7h","author":{"login":"badlogic"},"authorAssociation":"OWNER","body":"Uhhm, I will need more info here, as this breals the protocol. What provider does that?\r\n","createdAt":"2026-01-31T19:01:35Z","includesCreatedEdit":false,"isMinimized":false,"minimizedReason":"","reactionGroups":[],"url":"https://github.com/badlogic/pi-mono/pull/1105#issuecomment-3829071585","viewerDidAuthor":true}],"commits":[{"authoredDate":"2026-01-31T01:11:22Z","authors":[{"email":"[REDACTED]","id":"MDQ6VXNlcjI5NTgxMzM=","login":"nicobailon","name":"Nico Bailon"}],"committedDate":"2026-01-31T01:13:11Z","messageBody":"","messageHeadline":"feat(ai,agent): add tool_exec_* events for custom providers","oid":"864920ff70e66ffb6260a35038147e32bf259abf"}],"files":[{"path":"packages/agent/CHANGELOG.md","additions":4,"deletions":0},{"path":"packages/agent/src/agent-loop.ts","additions":29,"deletions":0},{"path":"packages/ai/CHANGELOG.md","additions":4,"deletions":0},{"path":"packages/ai/src/types.ts","additions":14,"deletions":0}],"title":"feat(ai,agent): add tool_exec_* events for custom providers"}Planning full PR page and diff
I'm preparing to include the PR description, comments, commits, and changed files, and will retrieve the diff using gh pr diff 1105.
>gh pr diff 1105diff --git a/packages/agent/CHANGELOG.md b/packages/agent/CHANGELOG.md
index e0035d250..428e64f36 100644
--- a/packages/agent/CHANGELOG.md
+++ b/packages/agent/CHANGELOG.md
@@ -2,6 +2,10 @@
## [Unreleased]
+### Added
+
+- Forward `tool_exec_start`, `tool_exec_update`, `tool_exec_end` provider events as `tool_execution_*` agent events in `streamAssistantResponse`
+
## [0.50.7] - 2026-01-31
## [0.50.6] - 2026-01-30
diff --git a/packages/agent/src/agent-loop.ts b/packages/agent/src/agent-loop.ts
index 753466aca..53e6374d9 100644
--- a/packages/agent/src/agent-loop.ts
+++ b/packages/agent/src/agent-loop.ts
@@ -268,6 +268,35 @@ async function streamAssistantResponse(
}
break;
+ case "tool_exec_start":
+ stream.push({
+ type: "tool_execution_start",
+ toolCallId: event.toolCallId,
+ toolName: event.toolName,
+ args: event.args,
+ });
+ break;
+
+ case "tool_exec_update":
+ stream.push({
+ type: "tool_execution_update",
+ toolCallId: event.toolCallId,
+ toolName: event.toolName,
+ args: event.args,
+ partialResult: event.partialResult,
+ });
+ break;
+
+ case "tool_exec_end":
+ stream.push({
+ type: "tool_execution_end",
+ toolCallId: event.toolCallId,
+ toolName: event.toolName,
+ result: event.result,
+ isError: event.isError,
+ });
+ break;
+
case "done":
case "error": {
const finalMessage = await response.result();
diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md
index 5ab1e9ca9..7afba9b76 100644
--- a/packages/ai/CHANGELOG.md
+++ b/packages/ai/CHANGELOG.md
@@ -2,6 +2,10 @@
## [Unreleased]
+### Added
+
+- Added `ToolExecResult` interface and `tool_exec_start`, `tool_exec_update`, `tool_exec_end` event variants to `AssistantMessageEvent` for custom providers that execute tools inside `streamSimple`
+
## [0.50.7] - 2026-01-31
## [0.50.6] - 2026-01-30
diff --git a/packages/ai/src/types.ts b/packages/ai/src/types.ts
index 3b9822f25..122013ed1 100644
--- a/packages/ai/src/types.ts
+++ b/packages/ai/src/types.ts
@@ -177,6 +177,11 @@ export interface Context {
tools?: Tool[];
}
+export interface ToolExecResult {
+ content: (TextContent | ImageContent)[];
+ details?: unknown;
+}
+
export type AssistantMessageEvent =
| { type: "start"; partial: AssistantMessage }
| { type: "text_start"; contentIndex: number; partial: AssistantMessage }
@@ -188,6 +193,15 @@ export type AssistantMessageEvent =
| { type: "toolcall_start"; contentIndex: number; partial: AssistantMessage }
| { type: "toolcall_delta"; contentIndex: number; delta: string; partial: AssistantMessage }
| { type: "toolcall_end"; contentIndex: number; toolCall: ToolCall; partial: AssistantMessage }
+ | { type: "tool_exec_start"; toolCallId: string; toolName: string; args: Record<string, unknown> }
+ | {
+ type: "tool_exec_update";
+ toolCallId: string;
+ toolName: string;
+ args: Record<string, unknown>;
+ partialResult: ToolExecResult;
+ }
+ | { type: "tool_exec_end"; toolCallId: string; toolName: string; result: ToolExecResult; isError: boolean }
| { type: "done"; reason: Extract<StopReason, "stop" | "length" | "toolUse">; message: AssistantMessage }
| { type: "error"; reason: Extract<StopReason, "aborted" | "error">; error: AssistantMessage };Planning full code reads
I'm preparing to read key files fully from the main branch to identify any linked issues or relevant code paths, as no references were found in PR bodies, comments, or commit messages.
Exploring agent event types
I'm focusing on the AgentEvent definitions in the agent types to understand how the new events integrate with the agent loop logic. I'll read the related types file next.
Analyzing event stream type updates and changelog attribution
I'm confirming the PR adds new event types to the assistant stream that require handling but aren't yet covered in existing switch cases. Also, the changelog entries exist but lack proper external contributor attribution for the PR author, which needs noting for review.
Identifying duplicate changelog sections and event handling nuances
The PR adds duplicate "Added" sections in both changelogs instead of appending to existing ones, violating formatting rules. Event handling for new tool_exec types forwards events without updating partial messages, which seems intended but might risk missing context if events occur out of expected order.
Checking event handling and type exports
Analyzed agent loop and event stream for tool_exec events, confirming partialResult handling needs addition; noted potential missing exports of ToolExecResult types in pi-ai package and possible need to update AssistantMessageEvent union across packages like TUI or coding-agent.
Assessing compatibility and documentation gaps
Reviewed event handling for new tool_exec events, confirming compatibility but noting potential protocol concerns raised by maintainers and missing documentation in ai package README; identified missing tests and invalid changelog entry needing mention in review.
>rg "AssistantMessageEvent" -n packagespackages/agent/src/proxy.ts:9: type AssistantMessageEvent,
packages/agent/src/proxy.ts:20:class ProxyMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
packages/agent/src/proxy.ts:36:export type ProxyAssistantMessageEvent =
packages/agent/src/proxy.ts:172: const proxyEvent = JSON.parse(data) as ProxyAssistantMessageEvent;
packages/agent/src/proxy.ts:212: proxyEvent: ProxyAssistantMessageEvent,
packages/agent/src/proxy.ts:214:): AssistantMessageEvent | undefined {
packages/agent/src/types.ts:2: AssistantMessageEvent,
packages/agent/src/types.ts:189: | { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
packages/ai/src/api-registry.ts:3: AssistantMessageEventStream,
packages/ai/src/api-registry.ts:15:) => AssistantMessageEventStream;
packages/ai/src/api-registry.ts:21:) => AssistantMessageEventStream;
packages/ai/src/types.ts:1:import type { AssistantMessageEventStream } from "./utils/event-stream.js";
packages/ai/src/types.ts:3:export type { AssistantMessageEventStream } from "./utils/event-stream.js";
packages/ai/src/types.ts:99:) => AssistantMessageEventStream;
packages/ai/src/types.ts:188:export type AssistantMessageEvent =
packages/ai/src/stream.ts:7: AssistantMessageEventStream,
packages/ai/src/stream.ts:38:): AssistantMessageEventStream {
packages/ai/src/stream.ts:56:): AssistantMessageEventStream {
packages/agent/test/agent-loop.test.ts:3: type AssistantMessageEvent,
packages/agent/test/agent-loop.test.ts:15:class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
packages/agent/test/agent.test.ts:1:import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@mariozechner/pi-ai";
packages/agent/test/agent.test.ts:5:// Mock stream that mimics AssistantMessageEventStream
packages/agent/test/agent.test.ts:6:class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
packages/ai/src/utils/event-stream.ts:1:import type { AssistantMessage, AssistantMessageEvent } from "../types.js";
packages/ai/src/utils/event-stream.ts:68:export class AssistantMessageEventStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
packages/ai/src/utils/event-stream.ts:84:/** Factory function for AssistantMessageEventStream (for use in extensions) */
packages/ai/src/utils/event-stream.ts:85:export function createAssistantMessageEventStream(): AssistantMessageEventStream {
packages/ai/src/utils/event-stream.ts:86: return new AssistantMessageEventStream();
packages/web-ui/CHANGELOG.md:166:- `ProxyAssistantMessageEvent` type
packages/ai/README.md:1095:- `stream<Provider>()` function returning `AssistantMessageEventStream`
packages/ai/CHANGELOG.md:62:- Added `createAssistantMessageEventStream()` factory function for use in extensions.
packages/ai/CHANGELOG.md:370:- **`event-stream.js` export**: `AssistantMessageEventStream` utility now exported from package index.
packages/ai/src/providers/amazon-bedrock.ts:40:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/amazon-bedrock.ts:64:): AssistantMessageEventStream => {
packages/ai/src/providers/amazon-bedrock.ts:65: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/amazon-bedrock.ts:196:): AssistantMessageEventStream => {
packages/ai/src/providers/amazon-bedrock.ts:232: stream: AssistantMessageEventStream,
packages/ai/src/providers/amazon-bedrock.ts:255: stream: AssistantMessageEventStream,
packages/ai/src/providers/amazon-bedrock.ts:328: stream: AssistantMessageEventStream,
packages/ai/src/providers/openai-responses-shared.ts:28:import type { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/openai-responses-shared.ts:264: stream: AssistantMessageEventStream,
packages/ai/test/openai-codex-stream.test.ts:22: it("streams SSE responses into AssistantMessageEventStream", async () => {
packages/ai/src/providers/anthropic.ts:26:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/anthropic.ts:161:): AssistantMessageEventStream => {
packages/ai/src/providers/anthropic.ts:162: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/anthropic.ts:370:): AssistantMessageEventStream => {
packages/ai/src/providers/google-gemini-cli.ts:23:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/google-gemini-cli.ts:383:): AssistantMessageEventStream => {
packages/ai/src/providers/google-gemini-cli.ts:384: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/google-gemini-cli.ts:851:): AssistantMessageEventStream => {
packages/ai/src/providers/google.ts:23:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/google.ts:52:): AssistantMessageEventStream => {
packages/ai/src/providers/google.ts:53: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/google.ts:275:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-completions.ts:29:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/openai-completions.ts:81:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-completions.ts:82: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/openai-completions.ts:328:): AssistantMessageEventStream => {
packages/ai/src/providers/google-vertex.ts:23:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/google-vertex.ts:64:): AssistantMessageEventStream => {
packages/ai/src/providers/google-vertex.ts:65: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/google-vertex.ts:287:): AssistantMessageEventStream => {
packages/ai/src/providers/azure-openai-responses.ts:14:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/azure-openai-responses.ts:59:): AssistantMessageEventStream => {
packages/ai/src/providers/azure-openai-responses.ts:60: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/azure-openai-responses.ts:124:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-responses.ts:15:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/openai-responses.ts:51:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-responses.ts:52: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/openai-responses.ts:117:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-codex-responses.ts:21:import { AssistantMessageEventStream } from "../utils/event-stream.js";
packages/ai/src/providers/openai-codex-responses.ts:106:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-codex-responses.ts:107: const stream = new AssistantMessageEventStream();
packages/ai/src/providers/openai-codex-responses.ts:224:): AssistantMessageEventStream => {
packages/ai/src/providers/openai-codex-responses.ts:298: stream: AssistantMessageEventStream,
packages/coding-agent/docs/custom-provider.md:268: type AssistantMessageEventStream,
packages/coding-agent/docs/custom-provider.md:273: createAssistantMessageEventStream,
packages/coding-agent/docs/custom-provider.md:280:): AssistantMessageEventStream {
packages/coding-agent/docs/custom-provider.md:281: const stream = createAssistantMessageEventStream();
packages/coding-agent/docs/custom-provider.md:467: ) => AssistantMessageEventStream;
packages/coding-agent/test/agent-session-concurrent.test.ts:9:import { type AssistantMessage, type AssistantMessageEvent, EventStream, getModel } from "@mariozechner/pi-ai";
packages/coding-agent/test/agent-session-concurrent.test.ts:18:// Mock stream that mimics AssistantMessageEventStream
packages/coding-agent/test/agent-session-concurrent.test.ts:19:class MockAssistantStream extends EventStream<AssistantMessageEvent, AssistantMessage> {
packages/coding-agent/src/core/model-registry.ts:7: type AssistantMessageEventStream,
packages/coding-agent/src/core/model-registry.ts:577: streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
packages/coding-agent/docs/json.md:34: | { type: "message_update"; message: AgentMessage; assistantMessageEvent: AssistantMessageEvent }
packages/coding-agent/src/core/extensions/types.ts:19: AssistantMessageEventStream,
packages/coding-agent/src/core/extensions/types.ts:940: streamSimple?: (model: Model<Api>, context: Context, options?: SimpleStreamOptions) => AssistantMessageEventStream;
packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts:14: type AssistantMessageEventStream,
packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts:16: createAssistantMessageEventStream,
packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts:270:): AssistantMessageEventStream {
packages/coding-agent/examples/extensions/custom-provider-gitlab-duo/index.ts:271: const stream = createAssistantMessageEventStream();
packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts:29: type AssistantMessageEventStream,
packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts:32: createAssistantMessageEventStream,
packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts:338:): AssistantMessageEventStream {
packages/coding-agent/examples/extensions/custom-provider-anthropic/index.ts:339: const stream = createAssistantMessageEventStream();Planning proxy event handling review
I'll read the full proxy code to analyze how it processes events, focusing on support for new events in proxied messages and checking related docs for expected behavior.
Noting proxy event type gaps
The proxy event types lack support for tool_exec events, which may affect custom providers using server-to-client streaming; this could require updating proxy event types to handle tool_exec events, but it's unclear if this falls within the current scope.
Planning doc review for event updates
I'm targeting the custom-provider docs in coding-agent to check if new tool_exec event types should be documented, considering exhaustive switches on event types may require updates; I'll verify mentions of AssistantMessageEvent there before deciding on documentation changes.
>wc -l packages/coding-agent/docs/custom-provider.md539 packages/coding-agent/docs/custom-provider.mdPreparing to read full file
Identifying missing event types in docs
The coding-agent docs currently lack mention of the new tool_exec_* events introduced for custom providers; these should be noted in the review with a recommendation to update relevant documentation and examples accordingly.
Assessing event handling for custom providers
Reviewing if proxy layers correctly support new assistant message event types related to tool executions; current proxy event mappings may lack these, but since custom providers mostly use simple streaming clients, no immediate update needed.
Evaluating new event types and test coverage
Identified new tool execution event types and their forwarding in the agent loop, but noted missing test coverage and incomplete docs updates for custom-provider event listings. Also found proxy protocol lacks support for these events, which blocks forwarding in streamProxy scenarios.
PR: https://github.com/badlogic/pi-mono/pull/1105 Changelog:
Added ... ([#1105](https://github.com/badlogic/pi-mono/pull/1105) by [@nicobailon](https://github.com/nicobailon)).Good:
Bad:
Ugly:
Questions or Assumptions:
Change summary:
Tests: