Every action the Copilot agent takes—thinking, writing code, running tools—is emitted as a session event you can subscribe to. This guide is a field-level reference for each event type so you know exactly what data to expect without reading the SDK source.
When streaming: true is set on a session, the SDK emits ephemeral events in real time (deltas, progress updates) alongside persisted events (complete messages, tool results). All events share a common envelope and carry a data payload whose shape depends on the event type.
sequenceDiagram
participant App as Your App
participant SDK as SDK Session
participant Agent as Copilot Agent
App->>SDK: send({ prompt })
SDK->>Agent: JSON-RPC
Agent-->>SDK: assistant.turn_start
SDK-->>App: event
loop Streaming response
Agent-->>SDK: assistant.message_delta (ephemeral)
SDK-->>App: event
end
Agent-->>SDK: assistant.message
SDK-->>App: event
loop Tool execution
Agent-->>SDK: tool.execution_start
SDK-->>App: event
Agent-->>SDK: tool.execution_complete
SDK-->>App: event
end
Agent-->>SDK: assistant.turn_end
SDK-->>App: event
Agent-->>SDK: session.idle (ephemeral)
SDK-->>App: event
| Concept | Description |
|---|---|
| Ephemeral event | Transient; streamed in real time but not persisted to the session log. Not replayed on session resume. |
| Persisted event | Saved to the session event log on disk. Replayed when resuming a session. |
| Delta event | An ephemeral streaming chunk (text or reasoning). Accumulate deltas to build the complete content. |
parentId chain |
Each event's parentId points to the previous event, forming a linked list you can walk. |
Every session event, regardless of type, includes these fields:
| Field | Type | Description |
|---|---|---|
id |
string (UUID v4) |
Unique event identifier |
timestamp |
string (ISO 8601) |
When the event was created |
parentId |
string | null |
ID of the previous event in the chain; null for the first event |
agentId |
string? |
Sub-agent instance ID for sub-agent-originated events; absent for root/main agent and session-level events |
ephemeral |
boolean? |
true for transient events; absent or false for persisted events |
type |
string |
Event type discriminator (see tables below) |
data |
object |
Event-specific payload |
Node.js / TypeScript
// All events
session.on((event) => {
console.log(event.type, event.data);
});
// Specific event type — data is narrowed automatically
session.on("assistant.message_delta", (event) => {
process.stdout.write(event.data.deltaContent);
});Python
from copilot import CopilotClient
from copilot.session_events import SessionEventType
client = CopilotClient()
session = None # assume session is created elsewhere
def handle(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
print(event.data.delta_content, end="", flush=True)
# session.on(handle)from copilot.session_events import SessionEventType
def handle(event):
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA:
print(event.data.delta_content, end="", flush=True)
session.on(handle)Go
package main
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-5.4",
Streaming: copilot.Bool(true),
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
})
_ = session
}session.On(func(event copilot.SessionEvent) {
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
}).NET
using GitHub.Copilot;
public static class StreamingEventsExample
{
public static async Task Example(CopilotSession session)
{
session.On<SessionEvent>(evt =>
{
if (evt is AssistantMessageDeltaEvent delta)
{
Console.Write(delta.Data.DeltaContent);
}
});
}
}session.On<SessionEvent>(evt =>
{
if (evt is AssistantMessageDeltaEvent delta)
{
Console.Write(delta.Data.DeltaContent);
}
});Java
// All events
session.on(event -> System.out.println(event.getType()));
// Specific event type — data is narrowed to the matching class
session.on(AssistantMessageDeltaEvent.class, event ->
System.out.print(event.getData().deltaContent())
);Tip
(Python / Go) These SDKs use separate, per-event data types (for example, AssistantMessageDeltaData), so only the relevant fields exist on each type.
[!TIP]
(.NET) The .NET SDK uses separate, strongly-typed data classes per event (e.g., AssistantMessageDeltaData), so only the relevant fields exist on each type.
[!TIP]
(TypeScript) The TypeScript SDK uses a discriminated union—when you match on event.type, the data payload is automatically narrowed to the correct shape.
A session can emit events before its create or resume call returns. The agent may already be working—especially on resume with continuePendingWork—and ephemeral events such as session.idle are never written to the session log, so getMessages cannot recover them afterwards. A subscription installed after the session handle exists misses that startup window.
Tip
(Rust) Client::prepare_session and Client::prepare_resume_session return a PreparedSession that owns the session's event channel before any protocol activity happens. Subscribe first, then call start().
use github_copilot_sdk::{Client, SessionConfig};
async fn create_without_missing_startup_events(
client: &Client,
) -> Result<(), github_copilot_sdk::Error> {
let prepared = client.prepare_session(
SessionConfig::default().with_event_buffer_capacity(2048),
)?;
// Installed before any wire activity: nothing is dropped for lack of a receiver.
let mut events = prepared.subscribe();
tokio::spawn(async move {
while let Ok(event) = events.recv().await {
println!("{}", event.event_type);
}
});
let session = prepared.start().await?;
let _ = session;
Ok(())
}prepare_* is synchronous and inert: it validates the buffer capacity, allocates a local channel, and does nothing else. No session is registered and nothing reaches the CLI until start() is first polled. Dropping a prepared session that was never started leaves no state behind and closes its subscriptions; dropping the start() future cancels the in-flight startup and unregisters the session, so a retry with the same session ID succeeds. Cleanup is scoped to the exact registration the abandoned startup owned, so it cannot evict a retry that has already taken over the same session ID.
Startup buffering is worth planning for:
- The event buffer is finite—512 events unless
event_buffer_capacityoverrides it. A capacity of0is rejected with an invalid-config error rather than clamped. - Slow subscribers observe a
Laggederror reporting how many events were skipped. They never apply backpressure to the session's event loop. - Consumers that need a lossless view of a large startup burst must either configure a capacity that covers it or drain the subscription concurrently with
start().
Note
For cloud sessions where the server assigns the session ID, the SDK cannot route notifications until the create response arrives and the ID is known. Events emitted before that point are not routable to any session. The guarantee is narrower: routed events are never dropped for lack of an installed receiver. Pin session_id on the config to get routing—and full pre-response coverage—from the first byte.
Sub-agent events share the parent session stream and include envelope-level agentId. Root/main agent events and session-level events omit agentId, so main-chat renderers can ignore assistant events where agentId is set and route those events to traces or progress UI instead.
TypeScript
import type { CopilotSession } from "@github/copilot-sdk";
export function subscribeParentResponse(session: CopilotSession): void {
session.on("assistant.message_delta", (event) => {
if (!event.agentId) {
process.stdout.write(event.data.deltaContent);
}
});
}Python
from copilot import CopilotSession, SessionEvent, SessionEventType
from copilot.session_events import AssistantMessageDeltaData
def subscribe_parent_response(session: CopilotSession) -> None:
def handle(event: SessionEvent) -> None:
if event.type == SessionEventType.ASSISTANT_MESSAGE_DELTA and event.agent_id is None:
data = event.data
if isinstance(data, AssistantMessageDeltaData):
print(data.delta_content, end="", flush=True)
session.on(handle)Go
package example
import (
"fmt"
copilot "github.com/github/copilot-sdk/go"
)
func subscribeParentResponse(session *copilot.Session) {
session.On(func(event copilot.SessionEvent) {
if event.AgentID != nil {
return
}
if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok {
fmt.Print(d.DeltaContent)
}
})
}.NET
using System;
using GitHub.Copilot;
static class ParentAgentResponseExample
{
public static void SubscribeParentResponse(CopilotSession session)
{
session.On<AssistantMessageDeltaEvent>(evt =>
{
if (evt.AgentId is null)
{
Console.Write(evt.Data.DeltaContent);
}
});
}
}Java
import com.github.copilot.CopilotSession;
import com.github.copilot.generated.AssistantMessageDeltaEvent;
final class ParentAgentResponseExample {
static void subscribeParentResponse(CopilotSession session) {
session.on(AssistantMessageDeltaEvent.class, event -> {
if (event.getAgentId() == null) {
System.out.print(event.getData().deltaContent());
}
});
}
}Rust
use github_copilot_sdk::session::Session;
async fn subscribe_parent_response(session: &Session) {
let mut events = session.subscribe();
while let Ok(event) = events.recv().await {
if event.event_type == "assistant.message_delta" && event.agent_id.is_none() {
if let Some(delta) = event.data.get("deltaContent").and_then(|v| v.as_str()) {
print!("{delta}");
}
}
}
}These events track the agent's response lifecycle—from turn start through streaming chunks to the final message.
Emitted when the agent begins processing a turn.
| Data Field | Type | Required | Description |
|---|---|---|---|
turnId |
string |
✅ | Turn identifier (typically a stringified turn number) |
interactionId |
string |
CAPI interaction ID for telemetry correlation |
Ephemeral. Short description of what the agent is currently doing, updated as it works.
| Data Field | Type | Required | Description |
|---|---|---|---|
intent |
string |
✅ | Human-readable intent (e.g., "Exploring codebase") |
Complete extended thinking block from the model. Emitted after reasoning is finished.
| Data Field | Type | Required | Description |
|---|---|---|---|
reasoningId |
string |
✅ | Unique identifier for this reasoning block |
content |
string |
✅ | The complete extended thinking text |
Ephemeral. Incremental chunk of the model's extended thinking, streamed in real time.
| Data Field | Type | Required | Description |
|---|---|---|---|
reasoningId |
string |
✅ | Matches the corresponding assistant.reasoning event |
deltaContent |
string |
✅ | Text chunk to append to reasoning content |
The assistant's complete response for this LLM call. May include tool invocation requests.
| Data Field | Type | Required | Description |
|---|---|---|---|
messageId |
string |
✅ | Unique identifier for this message |
content |
string |
✅ | The assistant's text response |
toolRequests |
ToolRequest[] |
Tool calls the assistant wants to make (see below) | |
reasoningOpaque |
string |
Encrypted extended thinking (Anthropic models); session-bound | |
reasoningText |
string |
Readable reasoning text from extended thinking | |
encryptedContent |
string |
Encrypted reasoning content (OpenAI models); session-bound | |
phase |
string |
Generation phase (e.g., "thinking" vs "response") |
|
outputTokens |
number |
Actual output token count from the API response | |
interactionId |
string |
CAPI interaction ID for telemetry | |
parentToolCallId |
string |
Deprecated. Use envelope-level agentId for sub-agent attribution |
ToolRequest fields:
| Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Unique ID for this tool call |
name |
string |
✅ | Tool name (e.g., "bash", "edit", "grep") |
arguments |
object |
Parsed arguments for the tool | |
type |
"function" | "custom" |
Call type; defaults to "function" when absent |
Ephemeral. Incremental chunk of the assistant's text response, streamed in real time.
| Data Field | Type | Required | Description |
|---|---|---|---|
messageId |
string |
✅ | Matches the corresponding assistant.message event |
deltaContent |
string |
✅ | Text chunk to append to the message |
parentToolCallId |
string |
Deprecated. Use envelope-level agentId for sub-agent attribution |
Emitted when the agent finishes a turn (all tool executions complete, final response delivered).
| Data Field | Type | Required | Description |
|---|---|---|---|
turnId |
string |
✅ | Matches the corresponding assistant.turn_start event |
Ephemeral. Token usage and cost information for an individual API call.
| Data Field | Type | Required | Description |
|---|---|---|---|
model |
string |
✅ | Model identifier (e.g., "gpt-5.4") |
inputTokens |
number |
Input tokens consumed | |
outputTokens |
number |
Output tokens produced | |
reasoningTokens |
number |
Output tokens used for reasoning/chain-of-thought (subset of outputTokens) |
|
cacheReadTokens |
number |
Tokens read from prompt cache | |
cacheWriteTokens |
number |
Tokens written to prompt cache | |
cacheExpiresAt |
string |
ISO 8601 timestamp when the prompt cache for this model call expires | |
contentFilterTriggered |
boolean |
Whether the response was blocked or truncated by content filtering (finish_reason === 'content_filter') |
|
finishReason |
string |
Model finish reason (e.g., "stop", "length", "tool_calls", "content_filter") |
|
cost |
number |
Model multiplier cost for billing | |
duration |
number |
API call duration in milliseconds | |
timeToFirstTokenMs |
number |
Time from request dispatch to first token received (streaming latency) | |
interTokenLatencyMs |
number |
Average latency between consecutive tokens (streaming throughput) | |
reasoningEffort |
string |
Reasoning effort level used for this call (e.g., "low", "medium", "high") |
|
initiator |
string |
What triggered this call (e.g., "sub-agent"); absent for user-initiated |
|
apiCallId |
string |
Completion ID from the provider (e.g., chatcmpl-abc123) |
|
serviceRequestId |
string |
Copilot service request ID (x-copilot-service-request-id) for CAPI log correlation |
|
apiEndpoint |
"/chat/completions" | "/v1/messages" | "/responses" | "ws:/responses" |
API endpoint used for the model call; useful for observability and cost attribution. ws:/responses is the websocket variant of the responses API |
|
providerCallId |
string |
GitHub request tracing ID (x-github-request-id) |
|
parentToolCallId |
string |
Deprecated. Use envelope-level agentId for sub-agent attribution |
|
quotaSnapshots |
Record<string, QuotaSnapshot> |
Per-quota resource usage, keyed by quota identifier | |
copilotUsage |
CopilotUsage |
Itemized token cost breakdown from the API |
Ephemeral. Low-level network progress indicator—total bytes received from the streaming API response.
| Data Field | Type | Required | Description |
|---|---|---|---|
totalResponseSizeBytes |
number |
✅ | Cumulative bytes received so far |
These events track the full lifecycle of each tool invocation—from the model requesting a tool call through execution to completion.
Emitted when a tool begins executing.
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Unique identifier for this tool call |
toolName |
string |
✅ | Name of the tool (e.g., "bash", "edit", "grep") |
arguments |
object |
Parsed arguments passed to the tool | |
mcpServerName |
string |
MCP server name, when the tool is provided by an MCP server | |
mcpToolName |
string |
Original tool name on the MCP server | |
parentToolCallId |
string |
Deprecated. Use envelope-level agentId for sub-agent attribution |
Ephemeral. Incremental output from a running tool (e.g., streaming bash output).
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Matches the corresponding tool.execution_start |
partialOutput |
string |
✅ | Incremental output chunk |
Ephemeral. Human-readable progress status from a running tool (e.g., MCP server progress notifications).
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Matches the corresponding tool.execution_start |
progressMessage |
string |
✅ | Progress status message |
Emitted when a tool finishes executing—successfully or with an error.
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Matches the corresponding tool.execution_start |
success |
boolean |
✅ | Whether execution succeeded |
model |
string |
Model that generated this tool call | |
interactionId |
string |
CAPI interaction ID | |
isUserRequested |
boolean |
true when the user explicitly requested this tool call |
|
result |
Result |
Present on success (see below) | |
error |
{ message, code? } |
Present on failure | |
toolTelemetry |
object |
Tool-specific telemetry (e.g., CodeQL check counts) | |
parentToolCallId |
string |
Deprecated. Use envelope-level agentId for sub-agent attribution |
Result fields:
| Field | Type | Required | Description |
|---|---|---|---|
content |
string |
✅ | Concise result sent to the LLM (may be truncated for token efficiency) |
detailedContent |
string |
Full result for display, preserving complete content like diffs | |
contents |
ContentBlock[] |
Structured content blocks (text, terminal, image, audio, resource) |
Emitted when the user explicitly requests a tool invocation (rather than the model choosing to call one).
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Unique identifier for this tool call |
toolName |
string |
✅ | Name of the tool the user wants to invoke |
arguments |
object |
Arguments for the invocation |
Ephemeral. The agent has finished all processing and is ready for the next message. This is the signal that a turn is fully complete.
| Data Field | Type | Required | Description |
|---|---|---|---|
aborted |
boolean |
True when the preceding turn was cancelled via abort signal |
An error occurred during session processing.
| Data Field | Type | Required | Description |
|---|---|---|---|
errorType |
string |
✅ | Error category (e.g., "authentication", "quota", "rate_limit") |
message |
string |
✅ | Human-readable error message |
stack |
string |
Error stack trace | |
statusCode |
number |
HTTP status code from the upstream request | |
providerCallId |
string |
GitHub request tracing ID for server-side log correlation |
Context window compaction has begun. Data payload is empty ({}).
Context window compaction finished.
| Data Field | Type | Required | Description |
|---|---|---|---|
success |
boolean |
✅ | Whether compaction succeeded |
error |
string |
Error message if compaction failed | |
preCompactionTokens |
number |
Tokens before compaction | |
postCompactionTokens |
number |
Tokens after compaction | |
preCompactionMessagesLength |
number |
Message count before compaction | |
messagesRemoved |
number |
Messages removed | |
tokensRemoved |
number |
Tokens removed | |
summaryContent |
string |
LLM-generated summary of compacted history | |
checkpointNumber |
number |
Checkpoint snapshot number created for recovery | |
checkpointPath |
string |
File path where the checkpoint was stored | |
compactionTokensUsed |
{ input, output, cachedInput } |
Token usage for the compaction LLM call | |
requestId |
string |
GitHub request tracing ID for the compaction call |
Ephemeral. The session's auto-generated title was updated.
| Data Field | Type | Required | Description |
|---|---|---|---|
title |
string |
✅ | New session title |
The session's working directory or repository context changed.
| Data Field | Type | Required | Description |
|---|---|---|---|
cwd |
string |
✅ | Current working directory |
gitRoot |
string |
Git repository root | |
repository |
string |
Repository in "owner/name" format |
|
branch |
string |
Current git branch |
Ephemeral. Context window utilization snapshot.
| Data Field | Type | Required | Description |
|---|---|---|---|
tokenLimit |
number |
✅ | Maximum tokens for the model's context window |
currentTokens |
number |
✅ | Current tokens in the context window |
messagesLength |
number |
✅ | Current message count in the conversation |
Session limits changed for the current accounting window. A null sessionLimits value means no limits are active.
| Data Field | Type | Required | Description |
|---|---|---|---|
sessionLimits |
SessionLimitsConfig | null |
✅ | Current session limits, or null when no limits are active |
sessionLimits.maxAiCredits |
number |
Maximum AI Credits allowed across the session's current accounting window |
Durable aggregate usage checkpoint used to reconstruct accounting when a session is resumed.
| Data Field | Type | Required | Description |
|---|---|---|---|
totalNanoAiu |
number |
✅ | Session-wide accumulated nano-AI units cost at checkpoint time |
totalPremiumRequests |
number |
Total number of premium API requests used at checkpoint time |
The agent has completed its assigned task.
| Data Field | Type | Required | Description |
|---|---|---|---|
summary |
string |
Summary of the completed task |
The session has ended.
| Data Field | Type | Required | Description |
|---|---|---|---|
shutdownType |
"routine" | "error" |
✅ | Normal shutdown or crash |
errorReason |
string |
Error description when shutdownType is "error" |
|
totalPremiumRequests |
number |
✅ | Total premium API requests used |
totalApiDurationMs |
number |
✅ | Cumulative API call time in milliseconds |
sessionStartTime |
number |
✅ | Unix timestamp (ms) when the session started |
codeChanges |
{ linesAdded, linesRemoved, filesModified } |
✅ | Aggregate code change metrics |
modelMetrics |
Record<string, ModelMetric> |
✅ | Per-model usage breakdown |
currentModel |
string |
Model selected at shutdown time |
These events are emitted when the agent needs approval or input from the user before continuing.
The agent needs permission to perform an action (run a command, write a file, etc.).
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToPermission() |
permissionRequest |
PermissionRequest |
✅ | Details of the permission being requested |
The permissionRequest is a discriminated union on kind:
kind |
Key Fields | Description |
|---|---|---|
"shell" |
fullCommandText, intention, commands[], possiblePaths[] |
Execute a shell command |
"write" |
fileName, diff, intention, newFileContents? |
Write/modify a file |
"read" |
path, intention |
Read a file or directory |
"mcp" |
serverName, toolName, toolTitle, args?, readOnly |
Call an MCP tool |
"url" |
url, intention |
Fetch a URL |
"memory" |
subject, fact, citations |
Store a memory |
"custom-tool" |
toolName, toolDescription, args? |
Call a custom tool |
All kind variants also include an optional toolCallId linking back to the tool call that triggered the request.
A permission request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding permission.requested |
result.kind |
string |
✅ | One of: "approved", "denied-by-rules", "denied-interactively-by-user", "denied-no-approval-rule-and-could-not-request-from-user", "denied-by-content-exclusion-policy" |
Ephemeral. The agent is asking the user a question.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToUserInput() |
question |
string |
✅ | The question to present to the user |
choices |
string[] |
Predefined choices for the user | |
allowFreeform |
boolean |
Whether free-form text input is allowed |
Ephemeral. A user input request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding user_input.requested |
Ephemeral. The agent needs structured form input from the user (MCP elicitation protocol).
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToElicitation() |
message |
string |
✅ | Description of what information is needed |
mode |
"form" |
Elicitation mode (currently only "form") |
|
requestedSchema |
{ type: "object", properties, required? } |
✅ | JSON Schema describing the form fields |
Ephemeral. An elicitation request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding elicitation.requested |
A custom agent was invoked as a sub-agent.
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Parent tool call that spawned this sub-agent |
agentName |
string |
✅ | Internal name of the sub-agent |
agentDisplayName |
string |
✅ | Human-readable display name |
agentDescription |
string |
✅ | Description of what the sub-agent does |
model |
string |
Model the sub-agent will run with, when known at start |
A sub-agent finished successfully.
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Matches the corresponding subagent.started |
agentName |
string |
✅ | Internal name |
agentDisplayName |
string |
✅ | Display name |
model |
string |
Model used by the sub-agent | |
durationMs |
number |
Wall-clock execution duration in milliseconds | |
totalTokens |
number |
Total input and output tokens consumed | |
totalToolCalls |
number |
Total tool calls made |
A sub-agent encountered an error.
| Data Field | Type | Required | Description |
|---|---|---|---|
toolCallId |
string |
✅ | Matches the corresponding subagent.started |
agentName |
string |
✅ | Internal name |
agentDisplayName |
string |
✅ | Display name |
error |
string |
✅ | Error message |
model |
string |
Model selected for the sub-agent, when known | |
durationMs |
number |
Wall-clock execution duration in milliseconds | |
totalTokens |
number |
Total input and output tokens consumed before failure | |
totalToolCalls |
number |
Total tool calls made before failure |
A custom agent was selected (inferred) to handle the current request.
| Data Field | Type | Required | Description |
|---|---|---|---|
agentName |
string |
✅ | Internal name of the selected agent |
agentDisplayName |
string |
✅ | Display name |
tools |
string[] | null |
✅ | Tool names available to this agent; null for all tools |
A custom agent was deselected, returning to the default agent. Data payload is empty ({}).
A skill was activated for the current conversation.
| Data Field | Type | Required | Description |
|---|---|---|---|
name |
string |
✅ | Skill name |
path |
string |
✅ | File path to the SKILL.md definition |
content |
string |
✅ | Full skill content injected into the conversation |
allowedTools |
string[] |
Tools auto-approved while this skill is active | |
pluginName |
string |
Plugin the skill originated from | |
pluginVersion |
string |
Plugin version |
The current turn was aborted.
| Data Field | Type | Required | Description |
|---|---|---|---|
reason |
string |
✅ | Why the turn was aborted (e.g., "user initiated") |
The user sent a message. Recorded for the session timeline.
| Data Field | Type | Required | Description |
|---|---|---|---|
content |
string |
✅ | The user's message text |
transformedContent |
string |
Transformed version after preprocessing | |
attachments |
Attachment[] |
File, directory, selection, blob, or GitHub reference attachments | |
source |
string |
Message source identifier | |
agentMode |
string |
Agent mode: "interactive", "plan", "autopilot", or "shell" |
|
interactionId |
string |
CAPI interaction ID |
A system or developer prompt was injected into the conversation.
| Data Field | Type | Required | Description |
|---|---|---|---|
content |
string |
✅ | The prompt text |
role |
"system" | "developer" |
✅ | Message role |
name |
string |
Source identifier | |
metadata |
{ promptVersion?, variables? } |
Prompt template metadata |
The agent wants to invoke an external tool (one provided by the SDK consumer).
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToExternalTool() |
sessionId |
string |
✅ | Session this request belongs to |
toolCallId |
string |
✅ | Tool call ID for this invocation |
toolName |
string |
✅ | Name of the external tool |
arguments |
object |
Arguments for the tool |
An external tool request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding external_tool.requested |
Ephemeral. The agent has created a plan and wants to exit plan mode.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToExitPlanMode() |
summary |
string |
✅ | Summary of the plan |
planContent |
string |
✅ | Full plan file content |
actions |
string[] |
✅ | Available user actions (e.g., approve, edit, reject) |
recommendedAction |
string |
✅ | Suggested action |
Ephemeral. An exit plan mode request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding exit_plan_mode.requested |
Ephemeral. A slash command was queued for execution.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this to respond via session.respondToQueuedCommand() |
command |
string |
✅ | The slash command text (e.g., /help, /clear) |
Ephemeral. A queued command was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding command.queued |
Ephemeral. The current session budget was exhausted and the runtime needs a user decision before continuing.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Use this ID when responding to the pending exhausted-limit request |
maxAiCredits |
number |
✅ | Configured max AI Credits for the current accounting window |
usedAiCredits |
number |
✅ | AI Credits already consumed in the current accounting window |
Ephemeral. A pending exhausted-limit request was resolved.
| Data Field | Type | Required | Description |
|---|---|---|---|
requestId |
string |
✅ | Matches the corresponding session_limits_exhausted.requested event |
response.action |
"add" | "set" | "unset" | "cancel" |
✅ | Action selected for the exhausted-limit request |
response.additionalAiCredits |
number |
AI Credits to add to the current max when response.action is "add" |
|
response.maxAiCredits |
number |
New absolute max AI Credits when response.action is "set" |
A typical agentic turn emits events in this order:
assistant.turn_start → Turn begins
├── assistant.intent → What the agent plans to do (ephemeral)
├── assistant.reasoning_delta → Streaming thinking chunks (ephemeral, repeated)
├── assistant.reasoning → Complete thinking block
├── assistant.message_delta → Streaming response chunks (ephemeral, repeated)
├── assistant.message → Complete response (may include toolRequests)
├── assistant.usage → Token usage for this API call (ephemeral)
│
├── [If tools were requested:]
│ ├── permission.requested → Needs user approval
│ ├── permission.completed → Approval result
│ ├── tool.execution_start → Tool begins
│ ├── tool.execution_partial_result → Streaming tool output (ephemeral, repeated)
│ ├── tool.execution_progress → Progress updates (ephemeral, repeated)
│ ├── tool.execution_complete → Tool finished
│ │
│ └── [Agent loops: more reasoning → message → tool calls...]
│
assistant.turn_end → Turn complete
session.idle → Ready for next message (ephemeral)
This table lists key data payload fields. Common envelope fields are documented above.
| Event Type | Ephemeral | Category | Key Data Fields |
|---|---|---|---|
assistant.turn_start |
Assistant | turnId, interactionId? |
|
assistant.intent |
✅ | Assistant | intent |
assistant.reasoning |
Assistant | reasoningId, content |
|
assistant.reasoning_delta |
✅ | Assistant | reasoningId, deltaContent |
assistant.streaming_delta |
✅ | Assistant | totalResponseSizeBytes |
assistant.message |
Assistant | messageId, content, toolRequests?, outputTokens?, phase? |
|
assistant.message_delta |
✅ | Assistant | messageId, deltaContent |
assistant.turn_end |
Assistant | turnId |
|
assistant.usage |
✅ | Assistant | model, apiEndpoint?, inputTokens?, outputTokens?, cost?, duration? |
tool.user_requested |
Tool | toolCallId, toolName, arguments? |
|
tool.execution_start |
Tool | toolCallId, toolName, arguments?, mcpServerName? |
|
tool.execution_partial_result |
✅ | Tool | toolCallId, partialOutput |
tool.execution_progress |
✅ | Tool | toolCallId, progressMessage |
tool.execution_complete |
Tool | toolCallId, success, result?, error? |
|
session.idle |
✅ | Session | aborted? |
session.error |
Session | errorType, message, statusCode? |
|
session.compaction_start |
Session | (empty) | |
session.compaction_complete |
Session | success, preCompactionTokens?, summaryContent? |
|
session.title_changed |
✅ | Session | title |
session.context_changed |
Session | cwd, gitRoot?, repository?, branch? |
|
session.usage_info |
✅ | Session | tokenLimit, currentTokens, messagesLength |
session.session_limits_changed |
Session | sessionLimits |
|
session.usage_checkpoint |
Session | totalNanoAiu, totalPremiumRequests? |
|
session.task_complete |
Session | summary? |
|
session.shutdown |
Session | shutdownType, codeChanges, modelMetrics |
|
permission.requested |
Permission | requestId, permissionRequest |
|
permission.completed |
Permission | requestId, result.kind |
|
user_input.requested |
✅ | User Input | requestId, question, choices? |
user_input.completed |
✅ | User Input | requestId |
elicitation.requested |
✅ | User Input | requestId, message, requestedSchema |
elicitation.completed |
✅ | User Input | requestId |
subagent.started |
Sub-Agent | toolCallId, agentName, agentDisplayName, model? |
|
subagent.completed |
Sub-Agent | toolCallId, agentName, agentDisplayName, model?, durationMs?, totalTokens?, totalToolCalls? |
|
subagent.failed |
Sub-Agent | toolCallId, agentName, error, model?, durationMs?, totalTokens?, totalToolCalls? |
|
subagent.selected |
Sub-Agent | agentName, agentDisplayName, tools |
|
subagent.deselected |
Sub-Agent | (empty) | |
skill.invoked |
Skill | name, path, content, allowedTools? |
|
abort |
Control | reason |
|
user.message |
User | content, attachments?, agentMode? |
|
system.message |
System | content, role |
|
external_tool.requested |
External Tool | requestId, toolName, arguments? |
|
external_tool.completed |
External Tool | requestId |
|
command.queued |
✅ | Command | requestId, command |
command.completed |
✅ | Command | requestId |
session_limits_exhausted.requested |
✅ | Session | requestId, maxAiCredits, usedAiCredits |
session_limits_exhausted.completed |
✅ | Session | requestId, response.action |
exit_plan_mode.requested |
✅ | Plan Mode | requestId, summary, planContent, actions |
exit_plan_mode.completed |
✅ | Plan Mode | requestId |