authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-02-28 19:10:08-08:00
committergravatar for jmarminge@gmail.comJulius Marminge <jmarminge@gmail.com> 2026-03-05 23:00:41-08:00
logd08cecf1262991407ccb8db8168197ce8f493ff2
tree28f91b001b642f2b9399d444944ba5e05d301154
parentafad39d125a319e5fa22e15bbc8615b3a6267aa4

Document Cursor ACP mapping in canonical runtime event spec

- Add Cursor ACP raw source types to `RuntimeEventRaw.source` - Define canonical mappings for ACP lifecycle, updates, permissions, and errors - Expand cross-provider equivalence table to include Cursor ACP and renumber sections

1 files changed, 62 insertions(+), 18 deletions(-)

EVENTS.md+62-18
......@@ -7,6 +7,7 @@ Define a **breaking** canonical provider-runtime event model that can represent
77- Claude Agent SDK types (`@anthropic-ai/claude-agent-sdk@0.2.62`)
88- Codex App Server protocol (`schema/json/*`, including `ServerNotification`, `ServerRequest`, `EventMsg`, and v2 payload schemas)
99- Codex TypeScript SDK thread events/items (`sdk/typescript/src/events.ts`, `items.ts`)
10- Cursor ACP protocol (`agent acp` JSON-RPC 2.0 over stdio)
1011
1112This is a mapping/spec document only (no downstream compatibility constraints).
1213
......@@ -24,7 +25,10 @@ type RuntimeEventRaw = {
2425 | "codex.eventmsg"
2526 | "claude.sdk.message"
2627 | "claude.sdk.permission"
27 | "codex.sdk.thread-event";
28 | "codex.sdk.thread-event"
29 | "cursor.acp.notification"
30 | "cursor.acp.request"
31 | "cursor.acp.response";
2832 method?: string;
2933 messageType?: string;
3034 payload: unknown;
......@@ -464,7 +468,47 @@ EventMsg is broader than server notification coverage. Important equivalents:
464468
465469---
466470
467## 5) Codex SDK Thread Events -> Canonical Mapping
471## 5) Cursor ACP -> Canonical Mapping
472
473### 5.1 ACP session lifecycle (JSON-RPC methods)
474
475| Cursor ACP method | Canonical V2 | Notes |
476| ------------------------------------ | ------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------- |
477| `initialize` (response) | `session.configured` | preserve `protocolVersion`, `agentCapabilities`, `authMethods` |
478| `authenticate` (response) | `auth.status` | success when empty result; errors map to `runtime.error(class=permission_error)` |
479| `session/new` (response) | `session.started` + `thread.started` + `session.state.changed(ready)` | persist `sessionId` in `providerRefs.providerSessionId`; preserve `modes` in payload/raw |
480| `session/load` (response) | `session.started` + `thread.started` + `session.state.changed(ready)` | resume path; note ACP requires `sessionId`, `cwd`, `mcpServers` in request |
481| `session/prompt` (response) | `turn.completed` | map `stopReason` (`end_turn`, `cancelled`, etc.) |
482| `session/cancel` (error `-32601`) | `runtime.warning` | currently unsupported in observed ACP version; adapter should fall back to process interrupt |
483
484### 5.2 ACP notifications (`session/update`)
485
486| Cursor ACP `update.sessionUpdate` | Canonical V2 | Notes |
487| --------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------- |
488| `available_commands_update` | `session.configured` | preserve `availableCommands`; optional secondary `runtime.warning` if list changes mid-turn |
489| `agent_thought_chunk` | `content.delta` (`streamKind=reasoning_text`) | map `content.text` |
490| `agent_message_chunk` | `content.delta` (`streamKind=assistant_text`) | map `content.text` |
491| `tool_call` | `item.started` | map `toolCallId` -> `itemId`; dedupe starts because ACP may emit multiple `tool_call` updates for the same id |
492| `tool_call_update` (`in_progress`) | `item.updated` | include status + partial metadata |
493| `tool_call_update` (`completed`) | `item.completed` | include `rawOutput` summary when present (terminal stdout/stderr, search totals, etc.) |
494
495### 5.3 ACP server requests (permission)
496
497| Cursor ACP request method | Canonical V2 | Notes |
498| ------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------- |
499| `session/request_permission` | `request.opened` | `requestType` usually `command_execution_approval` for `kind=execute`; preserve options (`allow/reject`) |
500| permission response sent by T3 | `request.resolved` | map T3 decision -> ACP `optionId` (`allow-once`, `allow-always`, `reject-once`) |
501
502### 5.4 ACP runtime/error mapping notes
503
5041. Unknown session on `session/prompt` (`-32603`) -> `runtime.error(class=provider_error)` and terminate active turn as failed.
5052. Invalid params / method-not-found (`-32602`, `-32601`) -> `runtime.error(class=validation_error)` or `runtime.warning` depending recoverability.
5063. Concurrent `session/prompt` calls can cancel earlier prompt (`stopReason=cancelled`) -> emit `turn.completed(state=cancelled)` for the superseded turn.
5074. Keep all ACP envelopes in `raw` with `source` set to `cursor.acp.notification` or `cursor.acp.request`.
508
509---
510
511## 6) Codex SDK Thread Events -> Canonical Mapping
468512
469513(For parity with direct Codex SDK integrations and to cross-reference app-server behavior.)
470514
......@@ -483,24 +527,24 @@ Codex SDK `ThreadItem.type` (`agent_message`, `reasoning`, `command_execution`,
483527
484528---
485529
486## 6) Cross-Provider Equivalence (Claude <-> Codex)
487
488| Concept | Codex source | Claude source | Canonical |
489| --------------------------- | -------------------------------------------- | ----------------------------------------------------------- | ------------------------------- |
490| Turn start | `turn/started` | first active response turn + `sendTurn` start | `turn.started` |
491| Turn completion | `turn/completed` | `result` | `turn.completed` |
492| Assistant text streaming | `item/agentMessage/delta` | `stream_event content_block_delta[text_delta]` | `content.delta(assistant_text)` |
493| Tool call start | `item/started` with tool item | `content_block_start tool_use/server_tool_use/mcp_tool_use` | `item.started` |
494| Tool call end | `item/completed` with tool item | `content_block_stop` | `item.completed` |
495| Approval required | server request `.../requestApproval` | `canUseTool` callback | `request.opened` |
496| Approval resolved | `serverRequest/resolved` + response decision | callback resolution decision | `request.resolved` |
497| Runtime error | `error` notification / failed turn | `result error_*` or assistant error | `runtime.error` |
498| Model reroute | `model/rerouted` | (none native, but could appear as result/meta) | `model.rerouted` |
499| Session capabilities/config | `session_configured`/init responses | `system:init` | `session.configured` |
530## 7) Cross-Provider Equivalence (Claude <-> Codex <-> Cursor ACP)
531
532| Concept | Codex source | Claude source | Cursor ACP source | Canonical |
533| --------------------------- | -------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------------- | ------------------------------- |
534| Turn start | `turn/started` | first active response turn + `sendTurn` start | `session/prompt` dispatch lifecycle | `turn.started` |
535| Turn completion | `turn/completed` | `result` | `session/prompt` response (`stopReason`) | `turn.completed` |
536| Assistant text streaming | `item/agentMessage/delta` | `stream_event content_block_delta[text_delta]` | `session/update:agent_message_chunk` | `content.delta(assistant_text)` |
537| Reasoning/thought streaming | `item/reasoning/textDelta` | reasoning deltas in `stream_event` | `session/update:agent_thought_chunk` | `content.delta(reasoning_text)` |
538| Tool call start | `item/started` with tool item | `content_block_start tool_use/server_tool_use/mcp_tool_use` | `session/update:tool_call` | `item.started` |
539| Tool call end | `item/completed` with tool item | `content_block_stop` | `session/update:tool_call_update(completed)` | `item.completed` |
540| Approval required | server request `.../requestApproval` | `canUseTool` callback | `session/request_permission` | `request.opened` |
541| Approval resolved | `serverRequest/resolved` + response decision | callback resolution decision | JSON-RPC response to permission request id | `request.resolved` |
542| Runtime error | `error` notification / failed turn | `result error_*` or assistant error | JSON-RPC error response (`-3260x` etc.) | `runtime.error` |
543| Session capabilities/config | `session_configured`/init responses | `system:init` | `initialize` / `available_commands_update` | `session.configured` |
500544
501545---
502546
503## 7) Implementation Guidance
547## 8) Implementation Guidance
504548
5055491. Emit one canonical event per source message minimum; emit additional derived events when needed (for example `runtime.error` + `turn.completed`).
5065502. Always attach `raw` payload so provider-specific detail is never lost.
......@@ -513,7 +557,7 @@ Codex SDK `ThreadItem.type` (`agent_message`, `reasoning`, `command_execution`,
513557
514558---
515559
516## 8) Summary of Breaking Schema Direction
560## 9) Summary of Breaking Schema Direction
517561
518562- Move from a small, chat-centric runtime union to an **item/lifecycle/protocol-complete canonical runtime model**.
519563- Normalize Claude and Codex onto the same event families (`turn`, `item`, `content`, `request`, `task`, `hook`, `state`), keeping provider specifics in `payload` + `raw`.