Skip to content

feat!: report tool calls through one contract, and fix the reports for every client - #544

Merged
nikita-ashihmin merged 3 commits into
nikita.ashikhmin/acp-patch-contentfrom
nikita.ashikhmin/air-acp/2-tool-call-contract
Sep 23, 2026
Merged

nikita-ashihmin merged 3 commits into
nikita.ashikhmin/acp-patch-contentfrom
nikita.ashikhmin/air-acp/2-tool-call-contract

Conversation

@nikita-ashihmin

Copy link
Copy Markdown
Collaborator

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 on main.

  • A tool_call_update omits top-level fields that did not change since the last report of the same tool call (ACP merge semantics). The _meta of each report stays complete for these clients.
  • Bug fixes: replayed command output sent once, generated images sent once, unique MCP startup tool call ids, dynamic tool results forwarded, no output after a tool call ended, permission requests no longer reset the status of a started tool call, an empty fuzzy search result clears the old locations, pending subagent notifications are no longer dropped or mutated.

For AIR only

  • One fact in one field: input in rawInput, the file text of an edit only in the diff, command output only through terminal_output_delta, stdin in terminal_input, no display copies of the input (rawInputRendering), no MCP progress lines.
  • The Markdown plan streams as plan_update with _meta.jetbrains.air.contentDelta (planContentDelta).
  • AIR-only keys move under _meta.jetbrains.air.* and are no longer sent to other clients. This is why the commit is marked feat!; only AIR reads these keys.

Structure

  • src/tool-calls/: one reporter per tool kind, one ClientCapabilities object, one AcpToolCallRenderer, and the changed-field filter. CodexToolCallMapper.ts is gone.
  • docs/air-extensions.md replaces 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.

…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
nikita-ashihmin requested a balanced review from Copilot September 23, 2026 12:33
@nikita-ashihmin
nikita-ashihmin force-pushed the nikita.ashikhmin/air-acp/2-tool-call-contract branch from ce18b05 to 500ca48 Compare September 23, 2026 12:38

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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 Medium severity

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
nikita-ashihmin merged commit 500ca48 into nikita.ashikhmin/acp-patch-content Sep 23, 2026
@nikita-ashihmin
nikita-ashihmin deleted the nikita.ashikhmin/air-acp/2-tool-call-contract branch September 23, 2026 13:03
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