feat!: report tool calls through one contract, and fix the reports for every client - #544
Merged
nikita-ashihmin merged 3 commits intoSep 23, 2026
Conversation
…r every client
Report tool calls through reporters and one renderer. The tool call
mapping moves to src/tool-calls/. A ToolReporter per tool kind reads
the Codex item once and returns tool facts. One AcpToolCallRenderer
puts each fact into one ACP field. It reads the capability choices
from one ClientCapabilities object. CodexToolCallMapper,
TerminalOutputMode, PlanCapabilities and permissions/presentation.ts
are removed.
Only AIR gets the AIR shape. A client is AIR when it declares
clientCapabilities._meta.jetbrains.air. A client that is not AIR, for
example Zed or a plain ACP client, gets the fields that origin/main
sent. ToolFacts.standard holds these fields where they differ.
Fixes for every client:
- Send only changed tool call fields. A new ToolCallReportingConnection
wraps the ACP client connection and runs every session update through
the ToolCallReports filter: live events, history replay, MCP startup,
permission and elicitation updates, plan review and async tasks. The
permission request tool call counts as a report. Appended output
chunks are never compared. ACP defines no merge for the keys of
_meta, so a client that is not AIR gets the whole _meta of each
report.
- Release the reported fields when a turn ends, when a native child
session ends, and after a cancelled or failed permission request.
Keep the small fields of a finished tool call, so a replayed tool
call does not repeat its status.
- Drop terminal and MCP output deltas that arrive after the tool call
finished.
- Send replayed command output once, and generated images once.
- Make the MCP startup tool call ids unique.
- Forward the result of a dynamic tool in content.
- Keep the shown title, status and kind of a started tool call in its
permission request.
- Send empty locations when a fuzzy search finds no file.
- Keep every notification of a pending subagent. The buffer merges
adjacent text deltas and is bounded by bytes, not by count. It stores
a copy of each notification.
- Send terminal_output_delta chunks to a client that declares no
terminal channel. Zed gets terminal_output for a command with a
terminal.
The AIR profile:
- Move the AIR metadata keys to _meta.jetbrains.air: the message phase,
the goal, the mode kind, the command action, the permission
presentation and the compaction record. AIR is not released, so the
adapter sends only the new keys.
- One fact goes into one field. A command sends its output in
terminal_output_delta, the raw stdin in _meta.terminal_input and its
end in terminal_exit, without rawOutput. Read, search and list output
is a result in content.
- rawInputRendering: AIR gets no display copy of readable input.
- planContentDelta: a streamed plan goes out as plan_update snapshots
and _meta.jetbrains.air.contentDelta appends (CodexPlanStream).
- The MCP result goes to rawOutput = {result, error}. AIR gets no MCP
progress. Other clients get the trimmed progress text.
- rawInput keeps the collaboration keys. Only spawnAgent gets
_meta.jetbrains.air.subagent.
- The plan review sends the plan text in rawInput.plan.
…air-extensions.md Merge the AIR extension docs into one file: the diff patch, the agent file change report, async tasks, goals, permissions, recommended config values, and the tool call contract. The old files are removed, and the README and the other docs link to the new sections. The compatibility rule limits the AIR extensions to AIR. A client that is not AIR gets the fields that the adapter sent before these extensions, with only the listed differences. The docs state the title and commandTitle exceptions of the contract, the terminal channel and the _meta filter of each client, and the Zed conventions. The plan of codex-acp is always streamed text. AIR also accepts a plan as a path to a file that it follows, but Codex keeps the plan only as text, so this adapter does not declare the planFile capability.
…call The tool call contract sent only the status in the permission request of a started MCP tool call. Before, the request also had kind execute. A client that reads the kind of the request, such as the MCP approval e2e test, no longer found it. Now the request has kind execute again for every client. The compatibility rule in docs/air-extensions.md now says that a permission request omits no field that it had before.
nikita-ashihmin
force-pushed
the
nikita.ashikhmin/air-acp/2-tool-call-contract
branch
from
September 23, 2026 12:38
ce18b05 to
500ca48
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
The pending-subagent buffer can exceed its documented hard byte cap because merged JSON escaping is undercounted.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Centralizes tool-call reporting behind capability-aware reporters/rendering while preserving standard ACP compatibility and introducing AIR-specific contracts.
Changes:
- Adds unified tool facts, rendering, filtering, and capability handling.
- Fixes output, replay, permission, image, search, and subagent reporting.
- Consolidates AIR extension documentation and expands behavioral tests.
| File | Description |
|---|---|
README.md |
Updates AIR feature links. |
docs/air-extensions.md |
Consolidates AIR contracts. |
docs/agent-file-change-report.md |
Removed; content consolidated. |
docs/async-tasks.md |
Removed; content consolidated. |
docs/diff-patch-extension.md |
Removed; content consolidated. |
docs/goal-extension.md |
Removed; content consolidated. |
docs/permission-extension.md |
Removed; content consolidated. |
docs/recommended-config-values-extension.md |
Removed; content consolidated. |
docs/session-compaction.md |
Links AIR compaction contract. |
docs/subagent-sessions.md |
Links AIR capability documentation. |
src/AgentMode.ts |
Scopes mode metadata to AIR. |
src/AirExtension.ts |
Adds AIR metadata helpers and keys. |
src/CodexAcpServer.ts |
Integrates capabilities and reporting pipeline. |
src/CodexCommands.ts |
Scopes command actions to AIR. |
src/CodexElicitationHandler.ts |
Uses unified elicitation reporting. |
src/CodexEventHandler.ts |
Routes events through reporters. |
src/CodexPlanStream.ts |
Implements capability-aware plan streaming. |
src/CodexToolCallMapper.ts |
Removed legacy mapper. |
src/ContentChunks.ts |
Moves message phase metadata under AIR. |
src/ContextCompactionMeta.ts |
Refactors compaction metadata facts. |
src/PlanCapabilities.ts |
Removed superseded capability helper. |
src/ResponseItemHistoryFallback.ts |
Uses unified rendering for fallback history. |
src/TerminalOutputMode.ts |
Removed superseded terminal helper. |
src/ToolCallReportingConnection.ts |
Filters all emitted tool-call reports. |
src/ToolCallReports.ts |
Tracks and removes unchanged fields. |
src/permissions/CodexApprovalHandler.ts |
Renders approval tool calls consistently. |
src/permissions/lifecycle.ts |
Tracks started commands for approvals. |
src/permissions/mcp.ts |
Uses elicitation reporter and AIR metadata. |
src/permissions/metadata.ts |
Namespaces AIR permission metadata. |
src/permissions/plan-review.ts |
Removed legacy plan-review helper. |
src/permissions/presentation.ts |
Removed legacy presentation mapping. |
src/subagents/CodexSubagentEventRouter.ts |
Buffers and releases child-session state. |
src/subagents/PendingNotificationBuffer.ts |
Adds byte-bounded notification buffering. |
src/tool-calls/AcpToolCallRenderer.ts |
Renders client-specific ACP reports. |
src/tool-calls/ClientCapabilities.ts |
Centralizes negotiated capabilities. |
src/tool-calls/ToolFacts.ts |
Defines normalized reporting facts. |
src/tool-calls/reporters/CollabAgentReporter.ts |
Reports collaboration calls. |
src/tool-calls/reporters/CommandReporter.ts |
Reports commands, output, and permissions. |
src/tool-calls/reporters/CompactionReporter.ts |
Reports synthetic compactions. |
src/tool-calls/reporters/DynamicToolReporter.ts |
Forwards dynamic-tool results. |
src/tool-calls/reporters/ElicitationReporter.ts |
Reports MCP elicitations. |
src/tool-calls/reporters/FileChangeReporter.ts |
Reports file diffs and approvals. |
src/tool-calls/reporters/FuzzySearchReporter.ts |
Reports fuzzy-search locations. |
src/tool-calls/reporters/GuardianReporter.ts |
Reports guardian reviews. |
src/tool-calls/reporters/ImageGenerationReporter.ts |
Reports generated images once. |
src/tool-calls/reporters/ImageViewReporter.ts |
Reports viewed images. |
src/tool-calls/reporters/McpStartupReporter.ts |
Creates unique startup-failure calls. |
src/tool-calls/reporters/McpToolReporter.ts |
Reports MCP lifecycle and results. |
src/tool-calls/reporters/PlanReviewReporter.ts |
Reports plan approval flow. |
src/tool-calls/reporters/SandboxPermissionReporter.ts |
Reports sandbox permission profiles. |
src/tool-calls/reporters/SubagentActivityReporter.ts |
Reports legacy subagent activity. |
src/tool-calls/reporters/ToolStatus.ts |
Maps Codex tool statuses. |
src/tool-calls/reporters/WebSearchReporter.ts |
Reports web-search actions. |
src/__tests__/CodexACPAgent/CodexAcpClient.test.ts |
Updates integration expectations. |
src/__tests__/CodexACPAgent/agent-file-change-report.test.ts |
Tests AIR-only advertisement. |
src/__tests__/CodexACPAgent/approval-events.test.ts |
Expands approval contract coverage. |
src/__tests__/CodexACPAgent/auth-error-events.test.ts |
Updates constructor and error tests. |
src/__tests__/CodexACPAgent/auth-status.test.ts |
Updates constructor usage. |
src/__tests__/CodexACPAgent/collab-agent-events.test.ts |
Tests filtering and child cleanup. |
src/__tests__/CodexACPAgent/command-output-once.test.ts |
Tests output deduplication. |
src/__tests__/CodexACPAgent/elicitation-events.test.ts |
Updates MCP approval expectations. |
src/__tests__/CodexACPAgent/file-change-events.test.ts |
Uses new file-change reporter. |
src/__tests__/CodexACPAgent/fuzzy-file-search-events.test.ts |
Verifies changed-field filtering. |
src/__tests__/CodexACPAgent/initialize.test.ts |
Tests AIR capability advertisement. |
src/__tests__/CodexACPAgent/load-session.test.ts |
Updates startup-call IDs. |
src/__tests__/CodexACPAgent/plan-events.test.ts |
Tests plan streaming modes. |
src/__tests__/CodexACPAgent/plan-review-events.test.ts |
Tests rendered plan approvals. |
src/__tests__/CodexACPAgent/response-item-history-fallback.test.ts |
Updates fallback capability tests. |
src/__tests__/CodexACPAgent/session-compaction.test.ts |
Aligns AIR compaction setup. |
src/__tests__/CodexACPAgent/terminal-output-events.test.ts |
Tests revised terminal channels. |
src/__tests__/CodexACPAgent/thread-goal-events.test.ts |
Updates AIR goal metadata. |
src/__tests__/CodexACPAgent/turn-diff-events.test.ts |
Updates handler construction. |
src/__tests__/CodexPlanStream.test.ts |
Tests plan snapshots and deltas. |
src/__tests__/PendingNotificationBuffer.test.ts |
Tests buffering and limits. |
src/__tests__/TerminalOutputMode.test.ts |
Removed obsolete tests. |
src/__tests__/ToolCallReportingConnection.test.ts |
Tests centralized filtering. |
src/__tests__/ToolCallReports.test.ts |
Tests report state and late output. |
src/__tests__/acp-test-utils.ts |
Updates shared session fixtures. |
src/__tests__/tool-calls/tool-call-contract.test.ts |
Tests AIR and standard contracts. |
src/__tests__/tool-calls/data/tool-calls-air.json |
Adds AIR contract snapshot. |
src/__tests__/tool-calls/data/tool-calls-zed.json |
Adds Zed contract snapshot. |
src/__tests__/CodexACPAgent/data/agent-message-phases.json |
Updates phase namespace. |
src/__tests__/CodexACPAgent/data/available-commands-build-in.json |
Updates command-action namespace. |
src/__tests__/CodexACPAgent/data/available-commands-skills.json |
Updates skill command metadata. |
src/__tests__/CodexACPAgent/data/command-list-files-with-path.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-list-files-without-path.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-read-file-with-path.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-search-no-query-no-path.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-search-with-path-only.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-search-with-query-and-path.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/command-search-with-query-only.json |
Updates field ordering. |
src/__tests__/CodexACPAgent/data/context-compaction-lifecycle.json |
Updates AIR compaction metadata. |
src/__tests__/CodexACPAgent/data/dynamic-tool-completed.json |
Adds dynamic-tool result content. |
src/__tests__/CodexACPAgent/data/dynamic-tool-in-progress.json |
Updates dynamic-tool shape. |
src/__tests__/CodexACPAgent/data/elicitation-tool-approval-all-persist.json |
Updates elicitation contract. |
src/__tests__/CodexACPAgent/data/elicitation-tool-approval-no-persist.json |
Updates elicitation contract. |
src/__tests__/CodexACPAgent/data/elicitation-tool-approval-session-only.json |
Updates elicitation contract. |
src/__tests__/CodexACPAgent/data/elicitation-url-accept.json |
Updates URL elicitation reports. |
src/__tests__/CodexACPAgent/data/file-change-add-multiple-files.json |
Updates file-change ordering. |
src/__tests__/CodexACPAgent/data/file-change-add-new-file.json |
Updates file-change ordering. |
src/__tests__/CodexACPAgent/data/file-change-add-raw-content.json |
Updates file-change ordering. |
src/__tests__/CodexACPAgent/data/file-change-delete-file.json |
Updates file-change ordering. |
src/__tests__/CodexACPAgent/data/file-change-delete-raw-content.json |
Updates file-change ordering. |
src/__tests__/CodexACPAgent/data/guardian-approval-review-completed-without-start.json |
Updates guardian output fields. |
src/__tests__/CodexACPAgent/data/guardian-approval-review-flow.json |
Updates guardian input/result separation. |
src/__tests__/CodexACPAgent/data/image-generation-completed-only.json |
Removes duplicate image output. |
src/__tests__/CodexACPAgent/data/image-generation-flow.json |
Removes duplicate image fields. |
src/__tests__/CodexACPAgent/data/load-session-history.json |
Updates replayed report shapes. |
src/__tests__/CodexACPAgent/data/load-session-response-item-history-fallback.json |
Updates fallback replay snapshots. |
src/__tests__/CodexACPAgent/data/mcp-tool-completed-with-logs.json |
Removes AIR MCP progress. |
src/__tests__/CodexACPAgent/data/mcp-tool-repeated-progress.json |
Removes repeated MCP progress. |
src/__tests__/CodexACPAgent/data/plan-completed-fallback.json |
Updates plan message metadata. |
src/__tests__/CodexACPAgent/data/plan-delta-fallback.json |
Captures streamed plan chunks. |
src/__tests__/CodexACPAgent/data/plan-deltas.json |
Captures streamed plan chunks. |
src/__tests__/CodexACPAgent/data/response-item-history-tool-names.json |
Updates fallback tool shape. |
src/__tests__/CodexACPAgent/data/session-compaction-legacy.json |
Updates AIR compaction snapshots. |
src/__tests__/CodexACPAgent/data/session-notices-replay.json |
Removes non-AIR compaction metadata. |
src/__tests__/CodexACPAgent/data/terminal-command-completed.json |
Adds terminal exit metadata. |
src/__tests__/CodexACPAgent/data/terminal-command-failed.json |
Updates failed-command terminal metadata. |
src/__tests__/CodexACPAgent/data/terminal-interaction-stdin.json |
Moves stdin to terminal input. |
src/__tests__/CodexACPAgent/data/terminal-output-parsed-command-legacy-delta.json |
Moves parsed output to content. |
src/__tests__/CodexACPAgent/data/thread-goal-cleared.json |
Updates AIR goal namespace. |
src/__tests__/CodexACPAgent/data/thread-goal-updated-multiline.json |
Updates AIR goal namespace. |
src/__tests__/CodexACPAgent/data/thread-goal-updated.json |
Updates AIR goal namespace. |
src/__tests__/CodexACPAgent/data/tool-call-command-names.json |
Updates command report shape. |
src/__tests__/CodexACPAgent/data/tool-call-completed-name.json |
Updates completed-command metadata. |
src/__tests__/CodexACPAgent/data/tool-call-dynamic-names.json |
Filters unchanged dynamic names. |
src/__tests__/CodexACPAgent/data/view-image-flow.json |
Updates image-view ordering. |
src/__tests__/CodexACPAgent/data/web-search-action-titles.json |
Removes duplicated web fields. |
src/__tests__/CodexACPAgent/data/web-search-start-and-complete.json |
Filters unchanged web fields. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+34
to
+38
| const delta = String((notification.params as Record<string, unknown>)[deltaField] ?? ""); | ||
| if (!this.reserve(Buffer.byteLength(delta, "utf8"))) return; | ||
| const params = last.params as Record<string, unknown>; | ||
| (last as {params: Record<string, unknown>}).params = {...params, [deltaField]: `${params[deltaField]}${delta}`}; | ||
| return; |
nikita-ashihmin
added a commit
that referenced
this pull request
Sep 23, 2026
…ffer cap The pending notification buffer of a subagent stores serialized JSON notifications and is bounded by bytes. For a new notification, it reserved the size of the serialized notification. For a text delta that it merged into the previous notification, it reserved only the UTF-8 size of the raw delta. A quote, a backslash, a newline, or a control character takes more bytes in JSON, so the stored notifications could exceed maxBytes. Now a merged delta reserves the growth of the serialized notification: the delta as a JSON string without its two quotes. A surrogate pair that a stream splits between two deltas counts with its escapes, so the count can be too large but never too small. A new test fills the buffer to 10 bytes under the cap and pushes a delta of three quotes and three newlines. The delta is 6 bytes of text and 12 bytes in JSON. Before the fix, the buffer kept it and exceeded the cap by 2 bytes. Now it drops the delta and keeps the next delta that fits. This answers the Copilot review comment on src/subagents/PendingNotificationBuffer.ts in PR #544.
nikita-ashihmin
deleted the
nikita.ashikhmin/air-acp/2-tool-call-contract
branch
September 23, 2026 13:03
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Summary
Step 2 of 3 of the AIR tool call stack, on top of #530 (diff patch).
For every client
Clients that do not declare
_meta.jetbrains.air(Zed, plain ACP clients) get the same information as onmain.tool_call_updateomits top-level fields that did not change since the last report of the same tool call (ACP merge semantics). The_metaof each report stays complete for these clients.For AIR only
rawInput, the file text of an edit only in the diff, command output only throughterminal_output_delta, stdin interminal_input, no display copies of the input (rawInputRendering), no MCP progress lines.plan_updatewith_meta.jetbrains.air.contentDelta(planContentDelta)._meta.jetbrains.air.*and are no longer sent to other clients. This is why the commit is markedfeat!; only AIR reads these keys.Structure
src/tool-calls/: one reporter per tool kind, oneClientCapabilitiesobject, oneAcpToolCallRenderer, and the changed-field filter.CodexToolCallMapper.tsis gone.docs/air-extensions.mdreplaces the per-extension AIR docs.The shared fixes and the AIR profile are one PR because the fixes live in the rewritten reporters; separating them would need throwaway code.
Tests
npm run typecheck,npm test: 800 passed.