diff --git a/.gitattributes b/.gitattributes index c1965c216..689a206be 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,8 @@ -.github/workflows/*.lock.yml linguist-generated=true merge=ours \ No newline at end of file +.github/workflows/*.lock.yml linguist-generated=true merge=ours + +# Generated files — keep LF line endings so codegen output is deterministic across platforms. +nodejs/src/generated/* eol=lf linguist-generated=true +dotnet/src/Generated/* eol=lf linguist-generated=true +python/copilot/generated/* eol=lf linguist-generated=true +go/generated_session_events.go eol=lf linguist-generated=true +go/rpc/generated_rpc.go eol=lf linguist-generated=true \ No newline at end of file diff --git a/.github/workflows/codegen-check.yml b/.github/workflows/codegen-check.yml index 33a7badcd..c7d295221 100644 --- a/.github/workflows/codegen-check.yml +++ b/.github/workflows/codegen-check.yml @@ -47,11 +47,6 @@ jobs: - name: Check for uncommitted changes run: | - # TODO: Remove this when https://github.com/github/copilot-sdk/issues/1031 is fixed - # Exclude go/generated_session_events.go from the check — it was intentionally - # reverted to avoid a breaking DataContent change (see #1031) and will be - # regenerated once that issue is resolved. - git checkout -- go/generated_session_events.go 2>/dev/null || true if [ -n "$(git status --porcelain)" ]; then echo "::error::Generated files are out of date. Run 'cd scripts/codegen && npm run generate' and commit the changes." git diff --stat diff --git a/docs/auth/byok.md b/docs/auth/byok.md index 823c376b1..d3d4e4106 100644 --- a/docs/auth/byok.md +++ b/docs/auth/byok.md @@ -130,7 +130,9 @@ func main() { panic(err) } - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` diff --git a/docs/features/custom-agents.md b/docs/features/custom-agents.md index 462161cfb..6c6455a02 100644 --- a/docs/features/custom-agents.md +++ b/docs/features/custom-agents.md @@ -506,17 +506,17 @@ func main() { }) session.On(func(event copilot.SessionEvent) { - switch event.Type { - case "subagent.started": - fmt.Printf("▶ Sub-agent started: %s\n", *event.Data.AgentDisplayName) - fmt.Printf(" Description: %s\n", *event.Data.AgentDescription) - fmt.Printf(" Tool call ID: %s\n", *event.Data.ToolCallID) - case "subagent.completed": - fmt.Printf("✅ Sub-agent completed: %s\n", *event.Data.AgentDisplayName) - case "subagent.failed": - fmt.Printf("❌ Sub-agent failed: %s — %v\n", *event.Data.AgentDisplayName, event.Data.Error) - case "subagent.selected": - fmt.Printf("🎯 Agent selected: %s\n", *event.Data.AgentDisplayName) + switch d := event.Data.(type) { + case *copilot.SubagentStartedData: + fmt.Printf("▶ Sub-agent started: %s\n", d.AgentDisplayName) + fmt.Printf(" Description: %s\n", d.AgentDescription) + fmt.Printf(" Tool call ID: %s\n", d.ToolCallID) + case *copilot.SubagentCompletedData: + fmt.Printf("✅ Sub-agent completed: %s\n", d.AgentDisplayName) + case *copilot.SubagentFailedData: + fmt.Printf("❌ Sub-agent failed: %s — %v\n", d.AgentDisplayName, d.Error) + case *copilot.SubagentSelectedData: + fmt.Printf("🎯 Agent selected: %s\n", d.AgentDisplayName) } }) @@ -530,17 +530,17 @@ func main() { ```go session.On(func(event copilot.SessionEvent) { - switch event.Type { - case "subagent.started": - fmt.Printf("▶ Sub-agent started: %s\n", *event.Data.AgentDisplayName) - fmt.Printf(" Description: %s\n", *event.Data.AgentDescription) - fmt.Printf(" Tool call ID: %s\n", *event.Data.ToolCallID) - case "subagent.completed": - fmt.Printf("✅ Sub-agent completed: %s\n", *event.Data.AgentDisplayName) - case "subagent.failed": - fmt.Printf("❌ Sub-agent failed: %s — %v\n", *event.Data.AgentDisplayName, event.Data.Error) - case "subagent.selected": - fmt.Printf("🎯 Agent selected: %s\n", *event.Data.AgentDisplayName) + switch d := event.Data.(type) { + case *copilot.SubagentStartedData: + fmt.Printf("▶ Sub-agent started: %s\n", d.AgentDisplayName) + fmt.Printf(" Description: %s\n", d.AgentDescription) + fmt.Printf(" Tool call ID: %s\n", d.ToolCallID) + case *copilot.SubagentCompletedData: + fmt.Printf("✅ Sub-agent completed: %s\n", d.AgentDisplayName) + case *copilot.SubagentFailedData: + fmt.Printf("❌ Sub-agent failed: %s — %v\n", d.AgentDisplayName, d.Error) + case *copilot.SubagentSelectedData: + fmt.Printf("🎯 Agent selected: %s\n", d.AgentDisplayName) } }) diff --git a/docs/features/streaming-events.md b/docs/features/streaming-events.md index 926af1b9e..9dde8f21b 100644 --- a/docs/features/streaming-events.md +++ b/docs/features/streaming-events.md @@ -137,8 +137,8 @@ func main() { }) session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { - fmt.Print(*event.Data.DeltaContent) + if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { + fmt.Print(d.DeltaContent) } }) _ = session @@ -148,8 +148,8 @@ func main() { ```go session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { - fmt.Print(*event.Data.DeltaContent) + if d, ok := event.Data.(*copilot.AssistantMessageDeltaData); ok { + fmt.Print(d.DeltaContent) } }) ``` diff --git a/docs/getting-started.md b/docs/getting-started.md index ab2893a27..e3dde4bf5 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -211,7 +211,9 @@ func main() { log.Fatal(err) } - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } os.Exit(0) } ``` @@ -406,10 +408,11 @@ func main() { // Listen for response chunks session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { - fmt.Print(*event.Data.DeltaContent) - } - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.AssistantMessageDeltaData: + fmt.Print(d.DeltaContent) + case *copilot.SessionIdleData: + _ = d fmt.Println() } }) @@ -604,10 +607,12 @@ func main() { // Filter by event type in your handler session.On(func(event copilot.SessionEvent) { - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.SessionIdleData: + _ = d fmt.Println("Session is idle") - } else if event.Type == "assistant.message" { - fmt.Println("Message:", *event.Data.Content) + case *copilot.AssistantMessageData: + fmt.Println("Message:", d.Content) } }) @@ -625,10 +630,12 @@ unsubscribe := session.On(func(event copilot.SessionEvent) { // Filter by event type in your handler session.On(func(event copilot.SessionEvent) { - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.SessionIdleData: + _ = d fmt.Println("Session is idle") - } else if event.Type == "assistant.message" { - fmt.Println("Message:", *event.Data.Content) + case *copilot.AssistantMessageData: + fmt.Println("Message:", d.Content) } }) @@ -897,10 +904,11 @@ func main() { } session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { - fmt.Print(*event.Data.DeltaContent) - } - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.AssistantMessageDeltaData: + fmt.Print(d.DeltaContent) + case *copilot.SessionIdleData: + _ = d fmt.Println() } }) @@ -1251,12 +1259,11 @@ func main() { } session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { - if event.Data.DeltaContent != nil { - fmt.Print(*event.Data.DeltaContent) - } - } - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.AssistantMessageDeltaData: + fmt.Print(d.DeltaContent) + case *copilot.SessionIdleData: + _ = d fmt.Println() } }) diff --git a/docs/setup/bundled-cli.md b/docs/setup/bundled-cli.md index 7a025385c..516b1fe21 100644 --- a/docs/setup/bundled-cli.md +++ b/docs/setup/bundled-cli.md @@ -130,7 +130,9 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` @@ -146,7 +148,9 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -fmt.Println(*response.Data.Content) +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) +} ``` diff --git a/docs/setup/local-cli.md b/docs/setup/local-cli.md index d5b168bd2..77d7a5e66 100644 --- a/docs/setup/local-cli.md +++ b/docs/setup/local-cli.md @@ -91,7 +91,9 @@ func main() { session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) - fmt.Println(*response.Data.Content) + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) + } } ``` @@ -105,7 +107,9 @@ defer client.Stop() session, _ := client.CreateSession(ctx, &copilot.SessionConfig{Model: "gpt-4.1"}) response, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Hello!"}) -fmt.Println(*response.Data.Content) +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println(d.Content) +} ``` diff --git a/go/README.md b/go/README.md index 14f8d3a0f..f60d39d51 100644 --- a/go/README.md +++ b/go/README.md @@ -57,12 +57,10 @@ func main() { // Set up event handler done := make(chan bool) session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message" { - if event.Data.Content != nil && event.Data.Content.String != nil { - fmt.Println(*event.Data.Content.String) - } - } - if event.Type == "session.idle" { + switch d := event.Data.(type) { + case *copilot.AssistantMessageData: + fmt.Println(d.Content) + case *copilot.SessionIdleData: close(done) } }) @@ -404,30 +402,22 @@ func main() { done := make(chan bool) session.On(func(event copilot.SessionEvent) { - if event.Type == "assistant.message_delta" { + switch d := event.Data.(type) { + case *copilot.AssistantMessageDeltaData: // Streaming message chunk - print incrementally - if event.Data.DeltaContent != nil { - fmt.Print(*event.Data.DeltaContent) - } - } else if event.Type == "assistant.reasoning_delta" { + fmt.Print(d.DeltaContent) + case *copilot.AssistantReasoningDeltaData: // Streaming reasoning chunk (if model supports reasoning) - if event.Data.DeltaContent != nil { - fmt.Print(*event.Data.DeltaContent) - } - } else if event.Type == "assistant.message" { + fmt.Print(d.DeltaContent) + case *copilot.AssistantMessageData: // Final message - complete content fmt.Println("\n--- Final message ---") - if event.Data.Content != nil && event.Data.Content.String != nil { - fmt.Println(*event.Data.Content.String) - } - } else if event.Type == "assistant.reasoning" { + fmt.Println(d.Content) + case *copilot.AssistantReasoningData: // Final reasoning content (if model supports reasoning) fmt.Println("--- Reasoning ---") - if event.Data.Content != nil && event.Data.Content.String != nil { - fmt.Println(*event.Data.Content.String) - } - } - if event.Type == "session.idle" { + fmt.Println(d.Content) + case *copilot.SessionIdleData: close(done) } }) diff --git a/go/client.go b/go/client.go index 188fae920..f8d29cc98 100644 --- a/go/client.go +++ b/go/client.go @@ -20,8 +20,8 @@ // } // // session.On(func(event copilot.SessionEvent) { -// if event.Type == "assistant.message" { -// fmt.Println(event.Data.Content) +// if d, ok := event.Data.(*copilot.AssistantMessageData); ok { +// fmt.Println(d.Content) // } // }) // diff --git a/go/generated_session_events.go b/go/generated_session_events.go index e3b6fa71e..4647679fa 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1,1051 +1,1828 @@ // AUTO-GENERATED FILE - DO NOT EDIT // Generated from: session-events.schema.json -// Code generated from JSON Schema using quicktype. DO NOT EDIT. -// To parse and unparse this JSON data, add this code to your project and do: -// -// sessionEvent, err := UnmarshalSessionEvent(bytes) -// bytes, err = sessionEvent.Marshal() - package copilot -import "bytes" -import "errors" -import "time" +import ( + "encoding/json" + "time" +) + +// SessionEventData is the interface implemented by all per-event data types. +type SessionEventData interface { + sessionEventData() +} + +// RawSessionEventData holds unparsed JSON data for unrecognized event types. +type RawSessionEventData struct { + Raw json.RawMessage +} + +func (RawSessionEventData) sessionEventData() {} -import "encoding/json" +// MarshalJSON returns the original raw JSON so round-tripping preserves the payload. +func (r RawSessionEventData) MarshalJSON() ([]byte, error) { return r.Raw, nil } +// SessionEvent represents a single session event with a typed data payload. +type SessionEvent struct { + // Unique event identifier (UUID v4), generated when the event is emitted. + ID string `json:"id"` + // ISO 8601 timestamp when the event was created. + Timestamp time.Time `json:"timestamp"` + // ID of the preceding event in the session. Null for the first event. + ParentID *string `json:"parentId"` + // When true, the event is transient and not persisted. + Ephemeral *bool `json:"ephemeral,omitempty"` + // The event type discriminator. + Type SessionEventType `json:"type"` + // Typed event payload. Use a type switch to access per-event fields. + Data SessionEventData `json:"-"` +} + +// UnmarshalSessionEvent parses JSON bytes into a SessionEvent. func UnmarshalSessionEvent(data []byte) (SessionEvent, error) { var r SessionEvent err := json.Unmarshal(data, &r) return r, err } +// Marshal serializes the SessionEvent to JSON. func (r *SessionEvent) Marshal() ([]byte, error) { return json.Marshal(r) } -type SessionEvent struct { - // Session initialization metadata including context and configuration - // - // Session resume metadata including current context and event count - // - // Notifies Mission Control that the session's remote steering capability has changed - // - // Error details for timeline display including message and optional diagnostic information - // - // Payload indicating the agent is idle; includes any background tasks still in flight - // - // Session title change payload containing the new display title - // - // Informational message for timeline display with categorization - // - // Warning message for timeline display with categorization - // - // Model change details including previous and new model identifiers - // - // Agent mode change details including previous and new modes - // - // Plan file operation details indicating what changed - // - // Workspace file change details including path and operation type - // - // Session handoff metadata including source, context, and repository information - // - // Conversation truncation statistics including token counts and removed content metrics - // - // Session rewind details including target event and count of removed events - // - // Session termination metrics including usage statistics, code changes, and shutdown - // reason - // - // Updated working directory and git context after the change - // - // Current context window usage statistics including token and message counts - // - // Context window breakdown at the start of LLM-powered conversation compaction - // - // Conversation compaction results including success status, metrics, and optional error - // details - // - // Task completion notification with summary from the agent - // - // Empty payload; the event signals that the pending message queue has changed - // - // Turn initialization metadata including identifier and interaction tracking - // - // Agent intent description for current activity or plan - // - // Assistant reasoning content for timeline display with complete thinking text - // - // Streaming reasoning delta for incremental extended thinking updates - // - // Streaming response progress with cumulative byte count - // - // Assistant response containing text content, optional tool requests, and interaction - // metadata - // - // Streaming assistant message delta for incremental response updates - // - // Turn completion metadata including the turn identifier - // - // LLM API call usage metrics including tokens, costs, quotas, and billing information - // - // Turn abort information including the reason for termination - // - // User-initiated tool invocation request with tool name and arguments - // - // Tool execution startup details including MCP server information when applicable - // - // Streaming tool execution output for incremental result display - // - // Tool execution progress notification with status message - // - // Tool execution completion results including success status, detailed output, and error - // information - // - // Skill invocation details including content, allowed tools, and plugin metadata - // - // Sub-agent startup details including parent tool call and agent information - // - // Sub-agent completion details for successful execution - // - // Sub-agent failure details including error message and agent information - // - // Custom agent selection details including name and available tools - // - // Empty payload; the event signals that the custom agent was deselected, returning to the - // default agent - // - // Hook invocation start details including type and input data - // - // Hook invocation completion details including output, success status, and error - // information - // - // System or developer message content with role and optional template metadata - // - // System-generated notification for runtime events like background task completion - // - // Permission request notification requiring client approval with request details - // - // Permission request completion notification signaling UI dismissal - // - // User input request notification with question and optional predefined choices - // - // User input request completion notification signaling UI dismissal - // - // Elicitation request; may be form-based (structured input) or URL-based (browser - // redirect) - // - // Elicitation request completion notification signaling UI dismissal - // - // Sampling request from an MCP server; contains the server name and a requestId for - // correlation - // - // Sampling request completion notification signaling UI dismissal - // - // OAuth authentication request for an MCP server - // - // MCP OAuth request completion notification - // - // External tool invocation request for client-side tool execution - // - // External tool completion notification signaling UI dismissal - // - // Queued slash command dispatch request for client execution - // - // Registered command dispatch request routed to the owning client - // - // Queued command completion notification signaling UI dismissal - // - // SDK command registration change notification - // - // Session capability change notification - // - // Plan approval request with plan content and available user actions - // - // Plan mode exit completion notification signaling UI dismissal - Data Data `json:"data"` - // When true, the event is transient and not persisted to the session event log on disk - Ephemeral *bool `json:"ephemeral,omitempty"` - // Unique event identifier (UUID v4), generated when the event is emitted - ID string `json:"id"` - // ID of the chronologically preceding event in the session, forming a linked chain. Null - // for the first event. - ParentID *string `json:"parentId"` - // ISO 8601 timestamp when the event was created - Timestamp time.Time `json:"timestamp"` - Type SessionEventType `json:"type"` +func (e *SessionEvent) UnmarshalJSON(data []byte) error { + type rawEvent struct { + ID string `json:"id"` + Timestamp time.Time `json:"timestamp"` + ParentID *string `json:"parentId"` + Ephemeral *bool `json:"ephemeral,omitempty"` + Type SessionEventType `json:"type"` + Data json.RawMessage `json:"data"` + } + var raw rawEvent + if err := json.Unmarshal(data, &raw); err != nil { + return err + } + e.ID = raw.ID + e.Timestamp = raw.Timestamp + e.ParentID = raw.ParentID + e.Ephemeral = raw.Ephemeral + e.Type = raw.Type + + switch raw.Type { + case SessionEventTypeSessionStart: + var d SessionStartData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionResume: + var d SessionResumeData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionRemoteSteerableChanged: + var d SessionRemoteSteerableChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionError: + var d SessionErrorData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionIdle: + var d SessionIdleData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionTitleChanged: + var d SessionTitleChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionInfo: + var d SessionInfoData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionWarning: + var d SessionWarningData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionModelChange: + var d SessionModelChangeData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionModeChanged: + var d SessionModeChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionPlanChanged: + var d SessionPlanChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionWorkspaceFileChanged: + var d SessionWorkspaceFileChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionHandoff: + var d SessionHandoffData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionTruncation: + var d SessionTruncationData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionSnapshotRewind: + var d SessionSnapshotRewindData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionShutdown: + var d SessionShutdownData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionContextChanged: + var d SessionContextChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionUsageInfo: + var d SessionUsageInfoData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionCompactionStart: + var d SessionCompactionStartData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionCompactionComplete: + var d SessionCompactionCompleteData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionTaskComplete: + var d SessionTaskCompleteData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeUserMessage: + var d UserMessageData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypePendingMessagesModified: + var d PendingMessagesModifiedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantTurnStart: + var d AssistantTurnStartData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantIntent: + var d AssistantIntentData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantReasoning: + var d AssistantReasoningData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantReasoningDelta: + var d AssistantReasoningDeltaData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantStreamingDelta: + var d AssistantStreamingDeltaData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantMessage: + var d AssistantMessageData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantMessageDelta: + var d AssistantMessageDeltaData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantTurnEnd: + var d AssistantTurnEndData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAssistantUsage: + var d AssistantUsageData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeAbort: + var d AbortData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeToolUserRequested: + var d ToolUserRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeToolExecutionStart: + var d ToolExecutionStartData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeToolExecutionPartialResult: + var d ToolExecutionPartialResultData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeToolExecutionProgress: + var d ToolExecutionProgressData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeToolExecutionComplete: + var d ToolExecutionCompleteData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSkillInvoked: + var d SkillInvokedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSubagentStarted: + var d SubagentStartedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSubagentCompleted: + var d SubagentCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSubagentFailed: + var d SubagentFailedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSubagentSelected: + var d SubagentSelectedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSubagentDeselected: + var d SubagentDeselectedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeHookStart: + var d HookStartData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeHookEnd: + var d HookEndData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSystemMessage: + var d SystemMessageData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSystemNotification: + var d SystemNotificationData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypePermissionRequested: + var d PermissionRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypePermissionCompleted: + var d PermissionCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeUserInputRequested: + var d UserInputRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeUserInputCompleted: + var d UserInputCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeElicitationRequested: + var d ElicitationRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeElicitationCompleted: + var d ElicitationCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSamplingRequested: + var d SamplingRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSamplingCompleted: + var d SamplingCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeMcpOauthRequired: + var d McpOauthRequiredData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeMcpOauthCompleted: + var d McpOauthCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeExternalToolRequested: + var d ExternalToolRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeExternalToolCompleted: + var d ExternalToolCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeCommandQueued: + var d CommandQueuedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeCommandExecute: + var d CommandExecuteData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeCommandCompleted: + var d CommandCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeCommandsChanged: + var d CommandsChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeCapabilitiesChanged: + var d CapabilitiesChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeExitPlanModeRequested: + var d ExitPlanModeRequestedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeExitPlanModeCompleted: + var d ExitPlanModeCompletedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionToolsUpdated: + var d SessionToolsUpdatedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionBackgroundTasksChanged: + var d SessionBackgroundTasksChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionSkillsLoaded: + var d SessionSkillsLoadedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionCustomAgentsUpdated: + var d SessionCustomAgentsUpdatedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionMcpServersLoaded: + var d SessionMcpServersLoadedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionMcpServerStatusChanged: + var d SessionMcpServerStatusChangedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + case SessionEventTypeSessionExtensionsLoaded: + var d SessionExtensionsLoadedData + if err := json.Unmarshal(raw.Data, &d); err != nil { + return err + } + e.Data = &d + default: + e.Data = &RawSessionEventData{Raw: raw.Data} + } + return nil } +func (e SessionEvent) MarshalJSON() ([]byte, error) { + type rawEvent struct { + ID string `json:"id"` + Timestamp time.Time `json:"timestamp"` + ParentID *string `json:"parentId"` + Ephemeral *bool `json:"ephemeral,omitempty"` + Type SessionEventType `json:"type"` + Data any `json:"data"` + } + return json.Marshal(rawEvent{ + ID: e.ID, + Timestamp: e.Timestamp, + ParentID: e.ParentID, + Ephemeral: e.Ephemeral, + Type: e.Type, + Data: e.Data, + }) +} + +// SessionEventType identifies the kind of session event. +type SessionEventType string + +const ( + SessionEventTypeSessionStart SessionEventType = "session.start" + SessionEventTypeSessionResume SessionEventType = "session.resume" + SessionEventTypeSessionRemoteSteerableChanged SessionEventType = "session.remote_steerable_changed" + SessionEventTypeSessionError SessionEventType = "session.error" + SessionEventTypeSessionIdle SessionEventType = "session.idle" + SessionEventTypeSessionTitleChanged SessionEventType = "session.title_changed" + SessionEventTypeSessionInfo SessionEventType = "session.info" + SessionEventTypeSessionWarning SessionEventType = "session.warning" + SessionEventTypeSessionModelChange SessionEventType = "session.model_change" + SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" + SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" + SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" + SessionEventTypeSessionHandoff SessionEventType = "session.handoff" + SessionEventTypeSessionTruncation SessionEventType = "session.truncation" + SessionEventTypeSessionSnapshotRewind SessionEventType = "session.snapshot_rewind" + SessionEventTypeSessionShutdown SessionEventType = "session.shutdown" + SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed" + SessionEventTypeSessionUsageInfo SessionEventType = "session.usage_info" + SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" + SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" + SessionEventTypeSessionTaskComplete SessionEventType = "session.task_complete" + SessionEventTypeUserMessage SessionEventType = "user.message" + SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" + SessionEventTypeAssistantTurnStart SessionEventType = "assistant.turn_start" + SessionEventTypeAssistantIntent SessionEventType = "assistant.intent" + SessionEventTypeAssistantReasoning SessionEventType = "assistant.reasoning" + SessionEventTypeAssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" + SessionEventTypeAssistantStreamingDelta SessionEventType = "assistant.streaming_delta" + SessionEventTypeAssistantMessage SessionEventType = "assistant.message" + SessionEventTypeAssistantMessageDelta SessionEventType = "assistant.message_delta" + SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end" + SessionEventTypeAssistantUsage SessionEventType = "assistant.usage" + SessionEventTypeAbort SessionEventType = "abort" + SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested" + SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start" + SessionEventTypeToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" + SessionEventTypeToolExecutionProgress SessionEventType = "tool.execution_progress" + SessionEventTypeToolExecutionComplete SessionEventType = "tool.execution_complete" + SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" + SessionEventTypeSubagentStarted SessionEventType = "subagent.started" + SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" + SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" + SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" + SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" + SessionEventTypeHookStart SessionEventType = "hook.start" + SessionEventTypeHookEnd SessionEventType = "hook.end" + SessionEventTypeSystemMessage SessionEventType = "system.message" + SessionEventTypeSystemNotification SessionEventType = "system.notification" + SessionEventTypePermissionRequested SessionEventType = "permission.requested" + SessionEventTypePermissionCompleted SessionEventType = "permission.completed" + SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" + SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" + SessionEventTypeElicitationRequested SessionEventType = "elicitation.requested" + SessionEventTypeElicitationCompleted SessionEventType = "elicitation.completed" + SessionEventTypeSamplingRequested SessionEventType = "sampling.requested" + SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed" + SessionEventTypeMcpOauthRequired SessionEventType = "mcp.oauth_required" + SessionEventTypeMcpOauthCompleted SessionEventType = "mcp.oauth_completed" + SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" + SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" + SessionEventTypeCommandQueued SessionEventType = "command.queued" + SessionEventTypeCommandExecute SessionEventType = "command.execute" + SessionEventTypeCommandCompleted SessionEventType = "command.completed" + SessionEventTypeCommandsChanged SessionEventType = "commands.changed" + SessionEventTypeCapabilitiesChanged SessionEventType = "capabilities.changed" + SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" + SessionEventTypeExitPlanModeCompleted SessionEventType = "exit_plan_mode.completed" + SessionEventTypeSessionToolsUpdated SessionEventType = "session.tools_updated" + SessionEventTypeSessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed" + SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded" + SessionEventTypeSessionCustomAgentsUpdated SessionEventType = "session.custom_agents_updated" + SessionEventTypeSessionMcpServersLoaded SessionEventType = "session.mcp_servers_loaded" + SessionEventTypeSessionMcpServerStatusChanged SessionEventType = "session.mcp_server_status_changed" + SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" +) + // Session initialization metadata including context and configuration -// -// # Session resume metadata including current context and event count -// -// # Notifies Mission Control that the session's remote steering capability has changed -// -// # Error details for timeline display including message and optional diagnostic information -// -// Payload indicating the agent is idle; includes any background tasks still in flight -// -// # Session title change payload containing the new display title -// -// # Informational message for timeline display with categorization -// -// # Warning message for timeline display with categorization -// -// # Model change details including previous and new model identifiers -// -// # Agent mode change details including previous and new modes -// -// # Plan file operation details indicating what changed -// -// # Workspace file change details including path and operation type -// -// # Session handoff metadata including source, context, and repository information -// -// # Conversation truncation statistics including token counts and removed content metrics -// -// # Session rewind details including target event and count of removed events -// -// Session termination metrics including usage statistics, code changes, and shutdown -// reason -// -// # Updated working directory and git context after the change -// -// # Current context window usage statistics including token and message counts -// -// # Context window breakdown at the start of LLM-powered conversation compaction -// -// Conversation compaction results including success status, metrics, and optional error -// details -// -// # Task completion notification with summary from the agent -// -// Empty payload; the event signals that the pending message queue has changed -// -// # Turn initialization metadata including identifier and interaction tracking -// -// # Agent intent description for current activity or plan -// -// # Assistant reasoning content for timeline display with complete thinking text -// -// # Streaming reasoning delta for incremental extended thinking updates -// -// # Streaming response progress with cumulative byte count -// -// Assistant response containing text content, optional tool requests, and interaction -// metadata -// -// # Streaming assistant message delta for incremental response updates -// -// # Turn completion metadata including the turn identifier -// -// # LLM API call usage metrics including tokens, costs, quotas, and billing information -// -// # Turn abort information including the reason for termination -// -// # User-initiated tool invocation request with tool name and arguments -// -// # Tool execution startup details including MCP server information when applicable -// -// # Streaming tool execution output for incremental result display -// -// # Tool execution progress notification with status message -// -// Tool execution completion results including success status, detailed output, and error -// information -// -// # Skill invocation details including content, allowed tools, and plugin metadata -// -// # Sub-agent startup details including parent tool call and agent information -// -// # Sub-agent completion details for successful execution -// -// # Sub-agent failure details including error message and agent information -// -// # Custom agent selection details including name and available tools -// -// Empty payload; the event signals that the custom agent was deselected, returning to the -// default agent -// -// # Hook invocation start details including type and input data -// -// Hook invocation completion details including output, success status, and error -// information -// -// # System or developer message content with role and optional template metadata -// -// # System-generated notification for runtime events like background task completion -// -// # Permission request notification requiring client approval with request details -// -// # Permission request completion notification signaling UI dismissal -// -// # User input request notification with question and optional predefined choices -// -// # User input request completion notification signaling UI dismissal -// -// Elicitation request; may be form-based (structured input) or URL-based (browser -// redirect) -// -// # Elicitation request completion notification signaling UI dismissal -// -// Sampling request from an MCP server; contains the server name and a requestId for -// correlation -// -// # Sampling request completion notification signaling UI dismissal -// -// # OAuth authentication request for an MCP server -// -// # MCP OAuth request completion notification -// -// # External tool invocation request for client-side tool execution -// -// # External tool completion notification signaling UI dismissal -// -// # Queued slash command dispatch request for client execution -// -// # Registered command dispatch request routed to the owning client -// -// # Queued command completion notification signaling UI dismissal -// -// # SDK command registration change notification -// -// # Session capability change notification -// -// # Plan approval request with plan content and available user actions -// -// Plan mode exit completion notification signaling UI dismissal -type Data struct { - // Whether the session was already in use by another client at start time - // - // Whether the session was already in use by another client at resume time - AlreadyInUse *bool `json:"alreadyInUse,omitempty"` - // Working directory and git context at session start - // - // Updated working directory and git context at resume time - // - // Additional context information for the handoff - Context *ContextUnion `json:"context"` - // Version string of the Copilot application - CopilotVersion *string `json:"copilotVersion,omitempty"` +type SessionStartData struct { + // Unique identifier for the session + SessionID string `json:"sessionId"` + // Schema version number for the session event format + Version float64 `json:"version"` // Identifier of the software producing the events (e.g., "copilot-agent") - Producer *string `json:"producer,omitempty"` - // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", - // "xhigh") - // - // Reasoning effort level after the model change, if applicable + Producer string `json:"producer"` + // Version string of the Copilot application + CopilotVersion string `json:"copilotVersion"` + // ISO 8601 timestamp when the session was created + StartTime time.Time `json:"startTime"` + // Model selected at session creation time, if any + SelectedModel *string `json:"selectedModel,omitempty"` + // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Working directory and git context at session start + Context *SessionStartDataContext `json:"context,omitempty"` + // Whether the session was already in use by another client at start time + AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Whether this session supports remote steering via Mission Control - // - // Whether this session now supports remote steering via Mission Control RemoteSteerable *bool `json:"remoteSteerable,omitempty"` - // Model selected at session creation time, if any - // +} + +func (*SessionStartData) sessionEventData() {} + +// Session resume metadata including current context and event count +type SessionResumeData struct { + // ISO 8601 timestamp when the session was resumed + ResumeTime time.Time `json:"resumeTime"` + // Total number of persisted events in the session at the time of resume + EventCount float64 `json:"eventCount"` // Model currently selected at resume time SelectedModel *string `json:"selectedModel,omitempty"` - // Unique identifier for the session - // - // Session ID that this external tool request belongs to - SessionID *string `json:"sessionId,omitempty"` - // ISO 8601 timestamp when the session was created - StartTime *time.Time `json:"startTime,omitempty"` - // Schema version number for the session event format - Version *float64 `json:"version,omitempty"` - // Total number of persisted events in the session at the time of resume - EventCount *float64 `json:"eventCount,omitempty"` - // ISO 8601 timestamp when the session was resumed - ResumeTime *time.Time `json:"resumeTime,omitempty"` - // Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", - // "context_limit", "query") - ErrorType *string `json:"errorType,omitempty"` + // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + ReasoningEffort *string `json:"reasoningEffort,omitempty"` + // Updated working directory and git context at resume time + Context *SessionResumeDataContext `json:"context,omitempty"` + // Whether the session was already in use by another client at resume time + AlreadyInUse *bool `json:"alreadyInUse,omitempty"` + // Whether this session supports remote steering via Mission Control + RemoteSteerable *bool `json:"remoteSteerable,omitempty"` +} + +func (*SessionResumeData) sessionEventData() {} + +// Notifies Mission Control that the session's remote steering capability has changed +type SessionRemoteSteerableChangedData struct { + // Whether this session now supports remote steering via Mission Control + RemoteSteerable bool `json:"remoteSteerable"` +} + +func (*SessionRemoteSteerableChangedData) sessionEventData() {} + +// Error details for timeline display including message and optional diagnostic information +type SessionErrorData struct { + // Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + ErrorType string `json:"errorType"` // Human-readable error message - // - // Human-readable informational message for display in the timeline - // - // Human-readable warning message for display in the timeline - // - // Message describing what information is needed from the user - Message *string `json:"message,omitempty"` - // GitHub request tracing ID (x-github-request-id header) for correlating with server-side - // logs - // - // GitHub request tracing ID (x-github-request-id header) for server-side log correlation - ProviderCallID *string `json:"providerCallId,omitempty"` + Message string `json:"message"` // Error stack trace, when available Stack *string `json:"stack,omitempty"` // HTTP status code from the upstream request, if applicable StatusCode *int64 `json:"statusCode,omitempty"` + // GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + ProviderCallID *string `json:"providerCallId,omitempty"` // Optional URL associated with this error that the user can open in a browser - // - // Optional URL associated with this message that the user can open in a browser - // - // Optional URL associated with this warning that the user can open in a browser - // - // URL to open in the user's browser (url mode only) URL *string `json:"url,omitempty"` +} + +func (*SessionErrorData) sessionEventData() {} + +// Payload indicating the session is fully idle with no background tasks in flight +type SessionIdleData struct { // True when the preceding agentic loop was cancelled via abort signal Aborted *bool `json:"aborted,omitempty"` - // Background tasks still running when the agent became idle - BackgroundTasks *BackgroundTasks `json:"backgroundTasks,omitempty"` +} + +func (*SessionIdleData) sessionEventData() {} + +// Session title change payload containing the new display title +type SessionTitleChangedData struct { // The new display title for the session - Title *string `json:"title,omitempty"` - // Category of informational message (e.g., "notification", "timing", "context_window", - // "mcp", "snapshot", "configuration", "authentication", "model") - InfoType *string `json:"infoType,omitempty"` + Title string `json:"title"` +} + +func (*SessionTitleChangedData) sessionEventData() {} + +// Informational message for timeline display with categorization +type SessionInfoData struct { + // Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") + InfoType string `json:"infoType"` + // Human-readable informational message for display in the timeline + Message string `json:"message"` + // Optional URL associated with this message that the user can open in a browser + URL *string `json:"url,omitempty"` +} + +func (*SessionInfoData) sessionEventData() {} + +// Warning message for timeline display with categorization +type SessionWarningData struct { // Category of warning (e.g., "subscription", "policy", "mcp") - WarningType *string `json:"warningType,omitempty"` - // Newly selected model identifier - NewModel *string `json:"newModel,omitempty"` + WarningType string `json:"warningType"` + // Human-readable warning message for display in the timeline + Message string `json:"message"` + // Optional URL associated with this warning that the user can open in a browser + URL *string `json:"url,omitempty"` +} + +func (*SessionWarningData) sessionEventData() {} + +// Model change details including previous and new model identifiers +type SessionModelChangeData struct { // Model that was previously selected, if any PreviousModel *string `json:"previousModel,omitempty"` + // Newly selected model identifier + NewModel string `json:"newModel"` // Reasoning effort level before the model change, if applicable PreviousReasoningEffort *string `json:"previousReasoningEffort,omitempty"` - // Agent mode after the change (e.g., "interactive", "plan", "autopilot") - NewMode *string `json:"newMode,omitempty"` + // Reasoning effort level after the model change, if applicable + ReasoningEffort *string `json:"reasoningEffort,omitempty"` +} + +func (*SessionModelChangeData) sessionEventData() {} + +// Agent mode change details including previous and new modes +type SessionModeChangedData struct { // Agent mode before the change (e.g., "interactive", "plan", "autopilot") - PreviousMode *string `json:"previousMode,omitempty"` + PreviousMode string `json:"previousMode"` + // Agent mode after the change (e.g., "interactive", "plan", "autopilot") + NewMode string `json:"newMode"` +} + +func (*SessionModeChangedData) sessionEventData() {} + +// Plan file operation details indicating what changed +type SessionPlanChangedData struct { // The type of operation performed on the plan file - // - // Whether the file was newly created or updated - Operation *Operation `json:"operation,omitempty"` + Operation SessionPlanChangedDataOperation `json:"operation"` +} + +func (*SessionPlanChangedData) sessionEventData() {} + +// Workspace file change details including path and operation type +type SessionWorkspaceFileChangedData struct { // Relative path within the session workspace files directory - // - // File path to the SKILL.md definition - Path *string `json:"path,omitempty"` + Path string `json:"path"` + // Whether the file was newly created or updated + Operation SessionWorkspaceFileChangedDataOperation `json:"operation"` +} + +func (*SessionWorkspaceFileChangedData) sessionEventData() {} + +// Session handoff metadata including source, context, and repository information +type SessionHandoffData struct { // ISO 8601 timestamp when the handoff occurred - HandoffTime *time.Time `json:"handoffTime,omitempty"` - // GitHub host URL for the source session (e.g., https://github.com or - // https://tenant.ghe.com) - Host *string `json:"host,omitempty"` - // Session ID of the remote session being handed off - RemoteSessionID *string `json:"remoteSessionId,omitempty"` - // Repository context for the handed-off session - // - // Repository identifier derived from the git remote URL ("owner/name" for GitHub, - // "org/project/repo" for Azure DevOps) - Repository *RepositoryUnion `json:"repository"` + HandoffTime time.Time `json:"handoffTime"` // Origin type of the session being handed off - SourceType *SourceType `json:"sourceType,omitempty"` + SourceType SessionHandoffDataSourceType `json:"sourceType"` + // Repository context for the handed-off session + Repository *SessionHandoffDataRepository `json:"repository,omitempty"` + // Additional context information for the handoff + Context *string `json:"context,omitempty"` // Summary of the work done in the source session - // - // Summary of the completed task, provided by the agent - // - // Summary of the plan that was created Summary *string `json:"summary,omitempty"` - // Number of messages removed by truncation - MessagesRemovedDuringTruncation *float64 `json:"messagesRemovedDuringTruncation,omitempty"` - // Identifier of the component that performed truncation (e.g., "BasicTruncator") - PerformedBy *string `json:"performedBy,omitempty"` - // Number of conversation messages after truncation - PostTruncationMessagesLength *float64 `json:"postTruncationMessagesLength,omitempty"` - // Total tokens in conversation messages after truncation - PostTruncationTokensInMessages *float64 `json:"postTruncationTokensInMessages,omitempty"` - // Number of conversation messages before truncation - PreTruncationMessagesLength *float64 `json:"preTruncationMessagesLength,omitempty"` - // Total tokens in conversation messages before truncation - PreTruncationTokensInMessages *float64 `json:"preTruncationTokensInMessages,omitempty"` + // Session ID of the remote session being handed off + RemoteSessionID *string `json:"remoteSessionId,omitempty"` + // GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) + Host *string `json:"host,omitempty"` +} + +func (*SessionHandoffData) sessionEventData() {} + +// Conversation truncation statistics including token counts and removed content metrics +type SessionTruncationData struct { // Maximum token count for the model's context window - TokenLimit *float64 `json:"tokenLimit,omitempty"` + TokenLimit float64 `json:"tokenLimit"` + // Total tokens in conversation messages before truncation + PreTruncationTokensInMessages float64 `json:"preTruncationTokensInMessages"` + // Number of conversation messages before truncation + PreTruncationMessagesLength float64 `json:"preTruncationMessagesLength"` + // Total tokens in conversation messages after truncation + PostTruncationTokensInMessages float64 `json:"postTruncationTokensInMessages"` + // Number of conversation messages after truncation + PostTruncationMessagesLength float64 `json:"postTruncationMessagesLength"` // Number of tokens removed by truncation - TokensRemovedDuringTruncation *float64 `json:"tokensRemovedDuringTruncation,omitempty"` - // Number of events that were removed by the rewind - EventsRemoved *float64 `json:"eventsRemoved,omitempty"` + TokensRemovedDuringTruncation float64 `json:"tokensRemovedDuringTruncation"` + // Number of messages removed by truncation + MessagesRemovedDuringTruncation float64 `json:"messagesRemovedDuringTruncation"` + // Identifier of the component that performed truncation (e.g., "BasicTruncator") + PerformedBy string `json:"performedBy"` +} + +func (*SessionTruncationData) sessionEventData() {} + +// Session rewind details including target event and count of removed events +type SessionSnapshotRewindData struct { // Event ID that was rewound to; all events after this one were removed - UpToEventID *string `json:"upToEventId,omitempty"` + UpToEventID string `json:"upToEventId"` + // Number of events that were removed by the rewind + EventsRemoved float64 `json:"eventsRemoved"` +} + +func (*SessionSnapshotRewindData) sessionEventData() {} + +// Session termination metrics including usage statistics, code changes, and shutdown reason +type SessionShutdownData struct { + // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") + ShutdownType SessionShutdownDataShutdownType `json:"shutdownType"` + // Error description when shutdownType is "error" + ErrorReason *string `json:"errorReason,omitempty"` + // Total number of premium API requests used during the session + TotalPremiumRequests float64 `json:"totalPremiumRequests"` + // Cumulative time spent in API calls during the session, in milliseconds + TotalAPIDurationMs float64 `json:"totalApiDurationMs"` + // Unix timestamp (milliseconds) when the session started + SessionStartTime float64 `json:"sessionStartTime"` // Aggregate code change metrics for the session - CodeChanges *CodeChanges `json:"codeChanges,omitempty"` - // Non-system message token count at shutdown - // - // Token count from non-system messages (user, assistant, tool) - // - // Token count from non-system messages (user, assistant, tool) at compaction start - // - // Token count from non-system messages (user, assistant, tool) after compaction - ConversationTokens *float64 `json:"conversationTokens,omitempty"` + CodeChanges SessionShutdownDataCodeChanges `json:"codeChanges"` + // Per-model usage breakdown, keyed by model identifier + ModelMetrics map[string]SessionShutdownDataModelMetricsValue `json:"modelMetrics"` // Model that was selected at the time of shutdown CurrentModel *string `json:"currentModel,omitempty"` // Total tokens in context window at shutdown - // - // Current number of tokens in the context window CurrentTokens *float64 `json:"currentTokens,omitempty"` - // Error description when shutdownType is "error" - ErrorReason *string `json:"errorReason,omitempty"` - // Per-model usage breakdown, keyed by model identifier - ModelMetrics map[string]ModelMetric `json:"modelMetrics,omitempty"` - // Unix timestamp (milliseconds) when the session started - SessionStartTime *float64 `json:"sessionStartTime,omitempty"` - // Whether the session ended normally ("routine") or due to a crash/fatal error ("error") - ShutdownType *ShutdownType `json:"shutdownType,omitempty"` // System message token count at shutdown - // - // Token count from system message(s) - // - // Token count from system message(s) at compaction start - // - // Token count from system message(s) after compaction SystemTokens *float64 `json:"systemTokens,omitempty"` + // Non-system message token count at shutdown + ConversationTokens *float64 `json:"conversationTokens,omitempty"` // Tool definitions token count at shutdown - // - // Token count from tool definitions - // - // Token count from tool definitions at compaction start - // - // Token count from tool definitions after compaction ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` - // Cumulative time spent in API calls during the session, in milliseconds - TotalAPIDurationMS *float64 `json:"totalApiDurationMs,omitempty"` - // Total number of premium API requests used during the session - TotalPremiumRequests *float64 `json:"totalPremiumRequests,omitempty"` - // Base commit of current git branch at session start time - BaseCommit *string `json:"baseCommit,omitempty"` - // Current git branch name - Branch *string `json:"branch,omitempty"` +} + +func (*SessionShutdownData) sessionEventData() {} + +// Updated working directory and git context after the change +type SessionContextChangedData struct { // Current working directory path - Cwd *string `json:"cwd,omitempty"` + Cwd string `json:"cwd"` // Root directory of the git repository, resolved via git rev-parse GitRoot *string `json:"gitRoot,omitempty"` + // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + Repository *string `json:"repository,omitempty"` + // Hosting platform type of the repository (github or ado) + HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + // Current git branch name + Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time HeadCommit *string `json:"headCommit,omitempty"` - // Hosting platform type of the repository (github or ado) - HostType *HostType `json:"hostType,omitempty"` + // Base commit of current git branch at session start time + BaseCommit *string `json:"baseCommit,omitempty"` +} + +func (*SessionContextChangedData) sessionEventData() {} + +// Current context window usage statistics including token and message counts +type SessionUsageInfoData struct { + // Maximum token count for the model's context window + TokenLimit float64 `json:"tokenLimit"` + // Current number of tokens in the context window + CurrentTokens float64 `json:"currentTokens"` + // Current number of messages in the conversation + MessagesLength float64 `json:"messagesLength"` + // Token count from system message(s) + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Token count from non-system messages (user, assistant, tool) + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Token count from tool definitions + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` // Whether this is the first usage_info event emitted in this session IsInitial *bool `json:"isInitial,omitempty"` - // Current number of messages in the conversation - MessagesLength *float64 `json:"messagesLength,omitempty"` - // Checkpoint snapshot number created for recovery - CheckpointNumber *float64 `json:"checkpointNumber,omitempty"` - // File path where the checkpoint was stored - CheckpointPath *string `json:"checkpointPath,omitempty"` - // Token usage breakdown for the compaction LLM call - CompactionTokensUsed *CompactionTokensUsed `json:"compactionTokensUsed,omitempty"` +} + +func (*SessionUsageInfoData) sessionEventData() {} + +// Context window breakdown at the start of LLM-powered conversation compaction +type SessionCompactionStartData struct { + // Token count from system message(s) at compaction start + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Token count from non-system messages (user, assistant, tool) at compaction start + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Token count from tool definitions at compaction start + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +} + +func (*SessionCompactionStartData) sessionEventData() {} + +// Conversation compaction results including success status, metrics, and optional error details +type SessionCompactionCompleteData struct { + // Whether compaction completed successfully + Success bool `json:"success"` // Error message if compaction failed - // - // Error details when the tool execution failed - // - // Error message describing why the sub-agent failed - // - // Error details when the hook failed - Error *ErrorUnion `json:"error"` - // Number of messages removed during compaction - MessagesRemoved *float64 `json:"messagesRemoved,omitempty"` + Error *string `json:"error,omitempty"` + // Total tokens in conversation before compaction + PreCompactionTokens *float64 `json:"preCompactionTokens,omitempty"` // Total tokens in conversation after compaction PostCompactionTokens *float64 `json:"postCompactionTokens,omitempty"` // Number of messages before compaction PreCompactionMessagesLength *float64 `json:"preCompactionMessagesLength,omitempty"` - // Total tokens in conversation before compaction - PreCompactionTokens *float64 `json:"preCompactionTokens,omitempty"` + // Number of messages removed during compaction + MessagesRemoved *float64 `json:"messagesRemoved,omitempty"` + // Number of tokens removed during compaction + TokensRemoved *float64 `json:"tokensRemoved,omitempty"` + // LLM-generated summary of the compacted conversation history + SummaryContent *string `json:"summaryContent,omitempty"` + // Checkpoint snapshot number created for recovery + CheckpointNumber *float64 `json:"checkpointNumber,omitempty"` + // File path where the checkpoint was stored + CheckpointPath *string `json:"checkpointPath,omitempty"` + // Token usage breakdown for the compaction LLM call + CompactionTokensUsed *SessionCompactionCompleteDataCompactionTokensUsed `json:"compactionTokensUsed,omitempty"` // GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - // - // Unique identifier for this permission request; used to respond via - // session.respondToPermission() - // - // Request ID of the resolved permission request; clients should dismiss any UI for this - // request - // - // Unique identifier for this input request; used to respond via - // session.respondToUserInput() - // - // Request ID of the resolved user input request; clients should dismiss any UI for this - // request - // - // Unique identifier for this elicitation request; used to respond via - // session.respondToElicitation() - // - // Request ID of the resolved elicitation request; clients should dismiss any UI for this - // request - // - // Unique identifier for this sampling request; used to respond via - // session.respondToSampling() - // - // Request ID of the resolved sampling request; clients should dismiss any UI for this - // request - // - // Unique identifier for this OAuth request; used to respond via - // session.respondToMcpOAuth() - // - // Request ID of the resolved OAuth request - // - // Unique identifier for this request; used to respond via session.respondToExternalTool() - // - // Request ID of the resolved external tool request; clients should dismiss any UI for this - // request - // - // Unique identifier for this request; used to respond via session.respondToQueuedCommand() - // - // Unique identifier; used to respond via session.commands.handlePendingCommand() - // - // Request ID of the resolved command request; clients should dismiss any UI for this - // request - // - // Unique identifier for this request; used to respond via session.respondToExitPlanMode() - // - // Request ID of the resolved exit plan mode request; clients should dismiss any UI for this - // request RequestID *string `json:"requestId,omitempty"` - // Whether compaction completed successfully - // + // Token count from system message(s) after compaction + SystemTokens *float64 `json:"systemTokens,omitempty"` + // Token count from non-system messages (user, assistant, tool) after compaction + ConversationTokens *float64 `json:"conversationTokens,omitempty"` + // Token count from tool definitions after compaction + ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` +} + +func (*SessionCompactionCompleteData) sessionEventData() {} + +// Task completion notification with summary from the agent +type SessionTaskCompleteData struct { + // Summary of the completed task, provided by the agent + Summary *string `json:"summary,omitempty"` // Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - // - // Whether the tool execution completed successfully - // - // Whether the hook completed successfully Success *bool `json:"success,omitempty"` - // LLM-generated summary of the compacted conversation history - SummaryContent *string `json:"summaryContent,omitempty"` - // Number of tokens removed during compaction - TokensRemoved *float64 `json:"tokensRemoved,omitempty"` - // The agent mode that was active when this message was sent - AgentMode *AgentMode `json:"agentMode,omitempty"` - // Files, selections, or GitHub references attached to the message - Attachments []Attachment `json:"attachments,omitempty"` +} + +func (*SessionTaskCompleteData) sessionEventData() {} + +// UserMessageData holds the payload for user.message events. +type UserMessageData struct { // The user's message text as displayed in the timeline - // - // The complete extended thinking text from the model - // - // The assistant's text response content - // - // Full content of the skill file, injected into the conversation for the model - // - // The system or developer prompt text - // - // The notification text, typically wrapped in XML tags - Content *string `json:"content,omitempty"` + Content string `json:"content"` + // Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + TransformedContent *string `json:"transformedContent,omitempty"` + // Files, selections, or GitHub references attached to the message + Attachments []UserMessageDataAttachmentsItem `json:"attachments,omitempty"` + // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) + Source *string `json:"source,omitempty"` + // The agent mode that was active when this message was sent + AgentMode *UserMessageDataAgentMode `json:"agentMode,omitempty"` // CAPI interaction ID for correlating this user message with its turn - // - // CAPI interaction ID for correlating this turn with upstream telemetry - // - // CAPI interaction ID for correlating this message with upstream telemetry - // - // CAPI interaction ID for correlating this tool execution with upstream telemetry InteractionID *string `json:"interactionId,omitempty"` - // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected - // messages that should be hidden from the user) - Source *string `json:"source,omitempty"` - // Transformed version of the message sent to the model, with XML wrapping, timestamps, and - // other augmentations for prompt caching - TransformedContent *string `json:"transformedContent,omitempty"` +} + +func (*UserMessageData) sessionEventData() {} + +// Empty payload; the event signals that the pending message queue has changed +type PendingMessagesModifiedData struct { +} + +func (*PendingMessagesModifiedData) sessionEventData() {} + +// Turn initialization metadata including identifier and interaction tracking +type AssistantTurnStartData struct { // Identifier for this turn within the agentic loop, typically a stringified turn number - // - // Identifier of the turn that has ended, matching the corresponding assistant.turn_start - // event - TurnID *string `json:"turnId,omitempty"` + TurnID string `json:"turnId"` + // CAPI interaction ID for correlating this turn with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` +} + +func (*AssistantTurnStartData) sessionEventData() {} + +// Agent intent description for current activity or plan +type AssistantIntentData struct { // Short description of what the agent is currently doing or planning to do - Intent *string `json:"intent,omitempty"` + Intent string `json:"intent"` +} + +func (*AssistantIntentData) sessionEventData() {} + +// Assistant reasoning content for timeline display with complete thinking text +type AssistantReasoningData struct { // Unique identifier for this reasoning block - // - // Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning - // event - ReasoningID *string `json:"reasoningId,omitempty"` + ReasoningID string `json:"reasoningId"` + // The complete extended thinking text from the model + Content string `json:"content"` +} + +func (*AssistantReasoningData) sessionEventData() {} + +// Streaming reasoning delta for incremental extended thinking updates +type AssistantReasoningDeltaData struct { + // Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event + ReasoningID string `json:"reasoningId"` // Incremental text chunk to append to the reasoning content - // - // Incremental text chunk to append to the message content - DeltaContent *string `json:"deltaContent,omitempty"` + DeltaContent string `json:"deltaContent"` +} + +func (*AssistantReasoningDeltaData) sessionEventData() {} + +// Streaming response progress with cumulative byte count +type AssistantStreamingDeltaData struct { // Cumulative total bytes received from the streaming response so far - TotalResponseSizeBytes *float64 `json:"totalResponseSizeBytes,omitempty"` + TotalResponseSizeBytes float64 `json:"totalResponseSizeBytes"` +} + +func (*AssistantStreamingDeltaData) sessionEventData() {} + +// Assistant response containing text content, optional tool requests, and interaction metadata +type AssistantMessageData struct { + // Unique identifier for this assistant message + MessageID string `json:"messageId"` + // The assistant's text response content + Content string `json:"content"` + // Tool invocations requested by the assistant in this message + ToolRequests []AssistantMessageDataToolRequestsItem `json:"toolRequests,omitempty"` + // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` + // Readable reasoning text from the model's extended thinking + ReasoningText *string `json:"reasoningText,omitempty"` // Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. EncryptedContent *string `json:"encryptedContent,omitempty"` - // Unique identifier for this assistant message - // - // Message ID this delta belongs to, matching the corresponding assistant.message event - MessageID *string `json:"messageId,omitempty"` - // Actual output token count from the API response (completion_tokens), used for accurate - // token accounting - // - // Number of output tokens produced + // Generation phase for phased-output models (e.g., thinking vs. response phases) + Phase *string `json:"phase,omitempty"` + // Actual output token count from the API response (completion_tokens), used for accurate token accounting OutputTokens *float64 `json:"outputTokens,omitempty"` + // CAPI interaction ID for correlating this message with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` // Tool call ID of the parent tool invocation when this event originates from a sub-agent - // - // Parent tool call ID when this usage originates from a sub-agent ParentToolCallID *string `json:"parentToolCallId,omitempty"` - // Generation phase for phased-output models (e.g., thinking vs. response phases) - Phase *string `json:"phase,omitempty"` - // Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped - // on resume. - ReasoningOpaque *string `json:"reasoningOpaque,omitempty"` - // Readable reasoning text from the model's extended thinking - ReasoningText *string `json:"reasoningText,omitempty"` - // Tool invocations requested by the assistant in this message - ToolRequests []ToolRequest `json:"toolRequests,omitempty"` - // Completion ID from the model provider (e.g., chatcmpl-abc123) - APICallID *string `json:"apiCallId,omitempty"` +} + +func (*AssistantMessageData) sessionEventData() {} + +// Streaming assistant message delta for incremental response updates +type AssistantMessageDeltaData struct { + // Message ID this delta belongs to, matching the corresponding assistant.message event + MessageID string `json:"messageId"` + // Incremental text chunk to append to the message content + DeltaContent string `json:"deltaContent"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` +} + +func (*AssistantMessageDeltaData) sessionEventData() {} + +// Turn completion metadata including the turn identifier +type AssistantTurnEndData struct { + // Identifier of the turn that has ended, matching the corresponding assistant.turn_start event + TurnID string `json:"turnId"` +} + +func (*AssistantTurnEndData) sessionEventData() {} + +// LLM API call usage metrics including tokens, costs, quotas, and billing information +type AssistantUsageData struct { + // Model identifier used for this API call + Model string `json:"model"` + // Number of input tokens consumed + InputTokens *float64 `json:"inputTokens,omitempty"` + // Number of output tokens produced + OutputTokens *float64 `json:"outputTokens,omitempty"` // Number of tokens read from prompt cache CacheReadTokens *float64 `json:"cacheReadTokens,omitempty"` // Number of tokens written to prompt cache CacheWriteTokens *float64 `json:"cacheWriteTokens,omitempty"` - // Per-request cost and usage data from the CAPI copilot_usage response field - CopilotUsage *CopilotUsage `json:"copilotUsage,omitempty"` // Model multiplier cost for billing purposes Cost *float64 `json:"cost,omitempty"` // Duration of the API call in milliseconds Duration *float64 `json:"duration,omitempty"` - // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for - // user-initiated calls - Initiator *string `json:"initiator,omitempty"` - // Number of input tokens consumed - InputTokens *float64 `json:"inputTokens,omitempty"` + // Time to first token in milliseconds. Only available for streaming requests + TtftMs *float64 `json:"ttftMs,omitempty"` // Average inter-token latency in milliseconds. Only available for streaming requests - InterTokenLatencyMS *float64 `json:"interTokenLatencyMs,omitempty"` - // Model identifier used for this API call - // - // Model identifier that generated this tool call - // - // Model used by the sub-agent - // - // Model used by the sub-agent (if any model calls succeeded before failure) - Model *string `json:"model,omitempty"` + InterTokenLatencyMs *float64 `json:"interTokenLatencyMs,omitempty"` + // What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls + Initiator *string `json:"initiator,omitempty"` + // Completion ID from the model provider (e.g., chatcmpl-abc123) + APICallID *string `json:"apiCallId,omitempty"` + // GitHub request tracing ID (x-github-request-id header) for server-side log correlation + ProviderCallID *string `json:"providerCallId,omitempty"` + // Parent tool call ID when this usage originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` // Per-quota resource usage snapshots, keyed by quota identifier - QuotaSnapshots map[string]QuotaSnapshot `json:"quotaSnapshots,omitempty"` - // Time to first token in milliseconds. Only available for streaming requests - TtftMS *float64 `json:"ttftMs,omitempty"` + QuotaSnapshots map[string]AssistantUsageDataQuotaSnapshotsValue `json:"quotaSnapshots,omitempty"` + // Per-request cost and usage data from the CAPI copilot_usage response field + CopilotUsage *AssistantUsageDataCopilotUsage `json:"copilotUsage,omitempty"` + // Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + ReasoningEffort *string `json:"reasoningEffort,omitempty"` +} + +func (*AssistantUsageData) sessionEventData() {} + +// Turn abort information including the reason for termination +type AbortData struct { // Reason the current turn was aborted (e.g., "user initiated") - Reason *string `json:"reason,omitempty"` - // Arguments for the tool invocation - // - // Arguments passed to the tool - // - // Arguments to pass to the external tool - Arguments interface{} `json:"arguments"` + Reason string `json:"reason"` +} + +func (*AbortData) sessionEventData() {} + +// User-initiated tool invocation request with tool name and arguments +type ToolUserRequestedData struct { // Unique identifier for this tool call - // - // Tool call ID this partial result belongs to - // - // Tool call ID this progress notification belongs to - // - // Unique identifier for the completed tool call - // - // Tool call ID of the parent tool invocation that spawned this sub-agent - // - // The LLM-assigned tool call ID that triggered this request; used by remote UIs to - // correlate responses - // - // Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id - // for remote UIs - // - // Tool call ID assigned to this external tool invocation - ToolCallID *string `json:"toolCallId,omitempty"` + ToolCallID string `json:"toolCallId"` // Name of the tool the user wants to invoke - // + ToolName string `json:"toolName"` + // Arguments for the tool invocation + Arguments any `json:"arguments,omitempty"` +} + +func (*ToolUserRequestedData) sessionEventData() {} + +// Tool execution startup details including MCP server information when applicable +type ToolExecutionStartData struct { + // Unique identifier for this tool call + ToolCallID string `json:"toolCallId"` // Name of the tool being executed - // - // Name of the external tool to invoke - ToolName *string `json:"toolName,omitempty"` + ToolName string `json:"toolName"` + // Arguments passed to the tool + Arguments any `json:"arguments,omitempty"` // Name of the MCP server hosting this tool, when the tool is an MCP tool - MCPServerName *string `json:"mcpServerName,omitempty"` + McpServerName *string `json:"mcpServerName,omitempty"` // Original tool name on the MCP server, when the tool is an MCP tool - MCPToolName *string `json:"mcpToolName,omitempty"` + McpToolName *string `json:"mcpToolName,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` +} + +func (*ToolExecutionStartData) sessionEventData() {} + +// Streaming tool execution output for incremental result display +type ToolExecutionPartialResultData struct { + // Tool call ID this partial result belongs to + ToolCallID string `json:"toolCallId"` // Incremental output chunk from the running tool - PartialOutput *string `json:"partialOutput,omitempty"` + PartialOutput string `json:"partialOutput"` +} + +func (*ToolExecutionPartialResultData) sessionEventData() {} + +// Tool execution progress notification with status message +type ToolExecutionProgressData struct { + // Tool call ID this progress notification belongs to + ToolCallID string `json:"toolCallId"` // Human-readable progress status message (e.g., from an MCP server) - ProgressMessage *string `json:"progressMessage,omitempty"` + ProgressMessage string `json:"progressMessage"` +} + +func (*ToolExecutionProgressData) sessionEventData() {} + +// Tool execution completion results including success status, detailed output, and error information +type ToolExecutionCompleteData struct { + // Unique identifier for the completed tool call + ToolCallID string `json:"toolCallId"` + // Whether the tool execution completed successfully + Success bool `json:"success"` + // Model identifier that generated this tool call + Model *string `json:"model,omitempty"` + // CAPI interaction ID for correlating this tool execution with upstream telemetry + InteractionID *string `json:"interactionId,omitempty"` // Whether this tool call was explicitly requested by the user rather than the assistant IsUserRequested *bool `json:"isUserRequested,omitempty"` // Tool execution result on success - // - // The result of the permission request - Result *Result `json:"result,omitempty"` + Result *ToolExecutionCompleteDataResult `json:"result,omitempty"` + // Error details when the tool execution failed + Error *ToolExecutionCompleteDataError `json:"error,omitempty"` // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) - ToolTelemetry map[string]interface{} `json:"toolTelemetry,omitempty"` + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` + // Tool call ID of the parent tool invocation when this event originates from a sub-agent + ParentToolCallID *string `json:"parentToolCallId,omitempty"` +} + +func (*ToolExecutionCompleteData) sessionEventData() {} + +// Skill invocation details including content, allowed tools, and plugin metadata +type SkillInvokedData struct { + // Name of the invoked skill + Name string `json:"name"` + // File path to the SKILL.md definition + Path string `json:"path"` + // Full content of the skill file, injected into the conversation for the model + Content string `json:"content"` // Tool names that should be auto-approved when this skill is active AllowedTools []string `json:"allowedTools,omitempty"` - // Description of the skill from its SKILL.md frontmatter - Description *string `json:"description,omitempty"` - // Name of the invoked skill - // - // Optional name identifier for the message source - Name *string `json:"name,omitempty"` // Name of the plugin this skill originated from, when applicable PluginName *string `json:"pluginName,omitempty"` // Version of the plugin this skill originated from, when applicable PluginVersion *string `json:"pluginVersion,omitempty"` - // Description of what the sub-agent does - AgentDescription *string `json:"agentDescription,omitempty"` + // Description of the skill from its SKILL.md frontmatter + Description *string `json:"description,omitempty"` +} + +func (*SkillInvokedData) sessionEventData() {} + +// Sub-agent startup details including parent tool call and agent information +type SubagentStartedData struct { + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` + // Internal name of the sub-agent + AgentName string `json:"agentName"` // Human-readable display name of the sub-agent - // - // Human-readable display name of the selected custom agent - AgentDisplayName *string `json:"agentDisplayName,omitempty"` + AgentDisplayName string `json:"agentDisplayName"` + // Description of what the sub-agent does + AgentDescription string `json:"agentDescription"` +} + +func (*SubagentStartedData) sessionEventData() {} + +// Sub-agent completion details for successful execution +type SubagentCompletedData struct { + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` // Internal name of the sub-agent - // - // Internal name of the selected custom agent - AgentName *string `json:"agentName,omitempty"` - // Wall-clock duration of the sub-agent execution in milliseconds - DurationMS *float64 `json:"durationMs,omitempty"` + AgentName string `json:"agentName"` + // Human-readable display name of the sub-agent + AgentDisplayName string `json:"agentDisplayName"` + // Model used by the sub-agent + Model *string `json:"model,omitempty"` + // Total number of tool calls made by the sub-agent + TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` // Total tokens (input + output) consumed by the sub-agent - // - // Total tokens (input + output) consumed before the sub-agent failed TotalTokens *float64 `json:"totalTokens,omitempty"` - // Total number of tool calls made by the sub-agent - // + // Wall-clock duration of the sub-agent execution in milliseconds + DurationMs *float64 `json:"durationMs,omitempty"` +} + +func (*SubagentCompletedData) sessionEventData() {} + +// Sub-agent failure details including error message and agent information +type SubagentFailedData struct { + // Tool call ID of the parent tool invocation that spawned this sub-agent + ToolCallID string `json:"toolCallId"` + // Internal name of the sub-agent + AgentName string `json:"agentName"` + // Human-readable display name of the sub-agent + AgentDisplayName string `json:"agentDisplayName"` + // Error message describing why the sub-agent failed + Error string `json:"error"` + // Model used by the sub-agent (if any model calls succeeded before failure) + Model *string `json:"model,omitempty"` // Total number of tool calls made before the sub-agent failed TotalToolCalls *float64 `json:"totalToolCalls,omitempty"` + // Total tokens (input + output) consumed before the sub-agent failed + TotalTokens *float64 `json:"totalTokens,omitempty"` + // Wall-clock duration of the sub-agent execution in milliseconds + DurationMs *float64 `json:"durationMs,omitempty"` +} + +func (*SubagentFailedData) sessionEventData() {} + +// Custom agent selection details including name and available tools +type SubagentSelectedData struct { + // Internal name of the selected custom agent + AgentName string `json:"agentName"` + // Human-readable display name of the selected custom agent + AgentDisplayName string `json:"agentDisplayName"` // List of tool names available to this agent, or null for all tools Tools []string `json:"tools"` +} + +func (*SubagentSelectedData) sessionEventData() {} + +// Empty payload; the event signals that the custom agent was deselected, returning to the default agent +type SubagentDeselectedData struct { +} + +func (*SubagentDeselectedData) sessionEventData() {} + +// Hook invocation start details including type and input data +type HookStartData struct { // Unique identifier for this hook invocation - // - // Identifier matching the corresponding hook.start event - HookInvocationID *string `json:"hookInvocationId,omitempty"` + HookInvocationID string `json:"hookInvocationId"` // Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - // - // Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - HookType *string `json:"hookType,omitempty"` + HookType string `json:"hookType"` // Input data passed to the hook - Input interface{} `json:"input"` + Input any `json:"input,omitempty"` +} + +func (*HookStartData) sessionEventData() {} + +// Hook invocation completion details including output, success status, and error information +type HookEndData struct { + // Identifier matching the corresponding hook.start event + HookInvocationID string `json:"hookInvocationId"` + // Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + HookType string `json:"hookType"` // Output data produced by the hook - Output interface{} `json:"output"` - // Metadata about the prompt template and its construction - Metadata *Metadata `json:"metadata,omitempty"` + Output any `json:"output,omitempty"` + // Whether the hook completed successfully + Success bool `json:"success"` + // Error details when the hook failed + Error *HookEndDataError `json:"error,omitempty"` +} + +func (*HookEndData) sessionEventData() {} + +// System or developer message content with role and optional template metadata +type SystemMessageData struct { + // The system or developer prompt text + Content string `json:"content"` // Message role: "system" for system prompts, "developer" for developer-injected instructions - Role *Role `json:"role,omitempty"` + Role SystemMessageDataRole `json:"role"` + // Optional name identifier for the message source + Name *string `json:"name,omitempty"` + // Metadata about the prompt template and its construction + Metadata *SystemMessageDataMetadata `json:"metadata,omitempty"` +} + +func (*SystemMessageData) sessionEventData() {} + +// System-generated notification for runtime events like background task completion +type SystemNotificationData struct { + // The notification text, typically wrapped in XML tags + Content string `json:"content"` // Structured metadata identifying what triggered this notification - Kind *KindClass `json:"kind,omitempty"` + Kind SystemNotificationDataKind `json:"kind"` +} + +func (*SystemNotificationData) sessionEventData() {} + +// Permission request notification requiring client approval with request details +type PermissionRequestedData struct { + // Unique identifier for this permission request; used to respond via session.respondToPermission() + RequestID string `json:"requestId"` // Details of the permission being requested - PermissionRequest *PermissionRequest `json:"permissionRequest,omitempty"` - // When true, this permission was already resolved by a permissionRequest hook and requires - // no client action + PermissionRequest PermissionRequestedDataPermissionRequest `json:"permissionRequest"` + // When true, this permission was already resolved by a permissionRequest hook and requires no client action ResolvedByHook *bool `json:"resolvedByHook,omitempty"` - // Whether the user can provide a free-form text response in addition to predefined choices - AllowFreeform *bool `json:"allowFreeform,omitempty"` +} + +func (*PermissionRequestedData) sessionEventData() {} + +// Permission request completion notification signaling UI dismissal +type PermissionCompletedData struct { + // Request ID of the resolved permission request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // The result of the permission request + Result PermissionCompletedDataResult `json:"result"` +} + +func (*PermissionCompletedData) sessionEventData() {} + +// User input request notification with question and optional predefined choices +type UserInputRequestedData struct { + // Unique identifier for this input request; used to respond via session.respondToUserInput() + RequestID string `json:"requestId"` + // The question or prompt to present to the user + Question string `json:"question"` // Predefined choices for the user to select from, if applicable Choices []string `json:"choices,omitempty"` - // The question or prompt to present to the user - Question *string `json:"question,omitempty"` + // Whether the user can provide a free-form text response in addition to predefined choices + AllowFreeform *bool `json:"allowFreeform,omitempty"` + // The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses + ToolCallID *string `json:"toolCallId,omitempty"` +} + +func (*UserInputRequestedData) sessionEventData() {} + +// User input request completion with the user's response +type UserInputCompletedData struct { + // Request ID of the resolved user input request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // The user's answer to the input request + Answer *string `json:"answer,omitempty"` + // Whether the answer was typed as free-form text rather than selected from choices + WasFreeform *bool `json:"wasFreeform,omitempty"` +} + +func (*UserInputCompletedData) sessionEventData() {} + +// Elicitation request; may be form-based (structured input) or URL-based (browser redirect) +type ElicitationRequestedData struct { + // Unique identifier for this elicitation request; used to respond via session.respondToElicitation() + RequestID string `json:"requestId"` + // Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs + ToolCallID *string `json:"toolCallId,omitempty"` // The source that initiated the request (MCP server name, or absent for agent-initiated) ElicitationSource *string `json:"elicitationSource,omitempty"` - // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to - // "form" when absent. - Mode *Mode `json:"mode,omitempty"` + // Message describing what information is needed from the user + Message string `json:"message"` + // Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. + Mode *ElicitationRequestedDataMode `json:"mode,omitempty"` // JSON Schema describing the form fields to present to the user (form mode only) - RequestedSchema *RequestedSchema `json:"requestedSchema,omitempty"` - // The JSON-RPC request ID from the MCP protocol - MCPRequestID *MCPRequestID `json:"mcpRequestId"` + RequestedSchema *ElicitationRequestedDataRequestedSchema `json:"requestedSchema,omitempty"` + // URL to open in the user's browser (url mode only) + URL *string `json:"url,omitempty"` +} + +func (*ElicitationRequestedData) sessionEventData() {} + +// Elicitation request completion with the user's response +type ElicitationCompletedData struct { + // Request ID of the resolved elicitation request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) + Action *ElicitationCompletedDataAction `json:"action,omitempty"` + // The submitted form data when action is 'accept'; keys match the requested schema fields + Content map[string]any `json:"content,omitempty"` +} + +func (*ElicitationCompletedData) sessionEventData() {} + +// Sampling request from an MCP server; contains the server name and a requestId for correlation +type SamplingRequestedData struct { + // Unique identifier for this sampling request; used to respond via session.respondToSampling() + RequestID string `json:"requestId"` // Name of the MCP server that initiated the sampling request - // + ServerName string `json:"serverName"` + // The JSON-RPC request ID from the MCP protocol + McpRequestID any `json:"mcpRequestId"` +} + +func (*SamplingRequestedData) sessionEventData() {} + +// Sampling request completion notification signaling UI dismissal +type SamplingCompletedData struct { + // Request ID of the resolved sampling request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` +} + +func (*SamplingCompletedData) sessionEventData() {} + +// OAuth authentication request for an MCP server +type McpOauthRequiredData struct { + // Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() + RequestID string `json:"requestId"` // Display name of the MCP server that requires OAuth - // - // Name of the MCP server whose status changed - ServerName *string `json:"serverName,omitempty"` + ServerName string `json:"serverName"` // URL of the MCP server that requires OAuth - ServerURL *string `json:"serverUrl,omitempty"` + ServerURL string `json:"serverUrl"` // Static OAuth client configuration, if the server specifies one - StaticClientConfig *StaticClientConfig `json:"staticClientConfig,omitempty"` + StaticClientConfig *McpOauthRequiredDataStaticClientConfig `json:"staticClientConfig,omitempty"` +} + +func (*McpOauthRequiredData) sessionEventData() {} + +// MCP OAuth request completion notification +type McpOauthCompletedData struct { + // Request ID of the resolved OAuth request + RequestID string `json:"requestId"` +} + +func (*McpOauthCompletedData) sessionEventData() {} + +// External tool invocation request for client-side tool execution +type ExternalToolRequestedData struct { + // Unique identifier for this request; used to respond via session.respondToExternalTool() + RequestID string `json:"requestId"` + // Session ID that this external tool request belongs to + SessionID string `json:"sessionId"` + // Tool call ID assigned to this external tool invocation + ToolCallID string `json:"toolCallId"` + // Name of the external tool to invoke + ToolName string `json:"toolName"` + // Arguments to pass to the external tool + Arguments any `json:"arguments,omitempty"` // W3C Trace Context traceparent header for the execute_tool span Traceparent *string `json:"traceparent,omitempty"` // W3C Trace Context tracestate header for the execute_tool span Tracestate *string `json:"tracestate,omitempty"` +} + +func (*ExternalToolRequestedData) sessionEventData() {} + +// External tool completion notification signaling UI dismissal +type ExternalToolCompletedData struct { + // Request ID of the resolved external tool request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` +} + +func (*ExternalToolCompletedData) sessionEventData() {} + +// Queued slash command dispatch request for client execution +type CommandQueuedData struct { + // Unique identifier for this request; used to respond via session.respondToQueuedCommand() + RequestID string `json:"requestId"` // The slash command text to be executed (e.g., /help, /clear) - // + Command string `json:"command"` +} + +func (*CommandQueuedData) sessionEventData() {} + +// Registered command dispatch request routed to the owning client +type CommandExecuteData struct { + // Unique identifier; used to respond via session.commands.handlePendingCommand() + RequestID string `json:"requestId"` // The full command text (e.g., /deploy production) - Command *string `json:"command,omitempty"` - // Raw argument string after the command name - Args *string `json:"args,omitempty"` + Command string `json:"command"` // Command name without leading / - CommandName *string `json:"commandName,omitempty"` + CommandName string `json:"commandName"` + // Raw argument string after the command name + Args string `json:"args"` +} + +func (*CommandExecuteData) sessionEventData() {} + +// Queued command completion notification signaling UI dismissal +type CommandCompletedData struct { + // Request ID of the resolved command request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` +} + +func (*CommandCompletedData) sessionEventData() {} + +// SDK command registration change notification +type CommandsChangedData struct { // Current list of registered SDK commands - Commands []DataCommand `json:"commands,omitempty"` + Commands []CommandsChangedDataCommandsItem `json:"commands"` +} + +func (*CommandsChangedData) sessionEventData() {} + +// Session capability change notification +type CapabilitiesChangedData struct { // UI capability changes - UI *UI `json:"ui,omitempty"` - // Available actions the user can take (e.g., approve, edit, reject) - Actions []string `json:"actions,omitempty"` + UI *CapabilitiesChangedDataUI `json:"ui,omitempty"` +} + +func (*CapabilitiesChangedData) sessionEventData() {} + +// Plan approval request with plan content and available user actions +type ExitPlanModeRequestedData struct { + // Unique identifier for this request; used to respond via session.respondToExitPlanMode() + RequestID string `json:"requestId"` + // Summary of the plan that was created + Summary string `json:"summary"` // Full content of the plan file - PlanContent *string `json:"planContent,omitempty"` + PlanContent string `json:"planContent"` + // Available actions the user can take (e.g., approve, edit, reject) + Actions []string `json:"actions"` // The recommended action for the user to take - RecommendedAction *string `json:"recommendedAction,omitempty"` - // Array of resolved skill metadata - Skills []Skill `json:"skills,omitempty"` - // Array of loaded custom agent metadata - Agents []DataAgent `json:"agents,omitempty"` - // Fatal errors from agent loading - Errors []string `json:"errors,omitempty"` - // Non-fatal warnings from agent loading - Warnings []string `json:"warnings,omitempty"` - // Array of MCP server status summaries - Servers []Server `json:"servers,omitempty"` - // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status *ServerStatus `json:"status,omitempty"` - // Array of discovered extensions and their status - Extensions []Extension `json:"extensions,omitempty"` + RecommendedAction string `json:"recommendedAction"` } -type DataAgent struct { - // Description of what the agent does - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier for the agent - ID string `json:"id"` - // Model override for this agent, if set - Model *string `json:"model,omitempty"` - // Internal name of the agent - Name string `json:"name"` - // Source location: user, project, inherited, remote, or plugin - Source string `json:"source"` - // List of tool names available to this agent - Tools []string `json:"tools"` - // Whether the agent can be selected by the user - UserInvocable bool `json:"userInvocable"` +func (*ExitPlanModeRequestedData) sessionEventData() {} + +// Plan mode exit completion with the user's approval decision and optional feedback +type ExitPlanModeCompletedData struct { + // Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request + RequestID string `json:"requestId"` + // Whether the plan was approved by the user + Approved *bool `json:"approved,omitempty"` + // Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') + SelectedAction *string `json:"selectedAction,omitempty"` + // Whether edits should be auto-approved without confirmation + AutoApproveEdits *bool `json:"autoApproveEdits,omitempty"` + // Free-form feedback from the user if they requested changes to the plan + Feedback *string `json:"feedback,omitempty"` } -// A user message attachment — a file, directory, code selection, blob, or GitHub reference -// -// # File attachment -// -// # Directory attachment -// -// # Code selection attachment from an editor -// -// # GitHub issue, pull request, or discussion reference -// -// Blob attachment with inline base64-encoded data -type Attachment struct { - // User-facing display name for the attachment - // - // User-facing display name for the selection - DisplayName *string `json:"displayName,omitempty"` - // Optional line range to scope the attachment to a specific section of the file - LineRange *LineRange `json:"lineRange,omitempty"` - // Absolute file path - // - // Absolute directory path - Path *string `json:"path,omitempty"` - // Attachment type discriminator - Type AttachmentType `json:"type"` - // Absolute path to the file containing the selection - FilePath *string `json:"filePath,omitempty"` - // Position range of the selection within the file - Selection *SelectionClass `json:"selection,omitempty"` - // The selected text content - Text *string `json:"text,omitempty"` - // Issue, pull request, or discussion number - Number *float64 `json:"number,omitempty"` - // Type of GitHub reference - ReferenceType *ReferenceType `json:"referenceType,omitempty"` - // Current state of the referenced item (e.g., open, closed, merged) - State *string `json:"state,omitempty"` - // Title of the referenced item - Title *string `json:"title,omitempty"` - // URL to the referenced item on GitHub - URL *string `json:"url,omitempty"` - // Base64-encoded content - Data *string `json:"data,omitempty"` - // MIME type of the inline data - MIMEType *string `json:"mimeType,omitempty"` +func (*ExitPlanModeCompletedData) sessionEventData() {} + +// SessionToolsUpdatedData holds the payload for session.tools_updated events. +type SessionToolsUpdatedData struct { + Model string `json:"model"` } -// Optional line range to scope the attachment to a specific section of the file -type LineRange struct { - // End line number (1-based, inclusive) - End float64 `json:"end"` - // Start line number (1-based) - Start float64 `json:"start"` +func (*SessionToolsUpdatedData) sessionEventData() {} + +// SessionBackgroundTasksChangedData holds the payload for session.background_tasks_changed events. +type SessionBackgroundTasksChangedData struct { } -// Position range of the selection within the file -type SelectionClass struct { - // End position of the selection - End End `json:"end"` - // Start position of the selection - Start Start `json:"start"` +func (*SessionBackgroundTasksChangedData) sessionEventData() {} + +// SessionSkillsLoadedData holds the payload for session.skills_loaded events. +type SessionSkillsLoadedData struct { + // Array of resolved skill metadata + Skills []SessionSkillsLoadedDataSkillsItem `json:"skills"` } -// End position of the selection -type End struct { - // End character offset within the line (0-based) - Character float64 `json:"character"` - // End line number (0-based) - Line float64 `json:"line"` +func (*SessionSkillsLoadedData) sessionEventData() {} + +// SessionCustomAgentsUpdatedData holds the payload for session.custom_agents_updated events. +type SessionCustomAgentsUpdatedData struct { + // Array of loaded custom agent metadata + Agents []SessionCustomAgentsUpdatedDataAgentsItem `json:"agents"` + // Non-fatal warnings from agent loading + Warnings []string `json:"warnings"` + // Fatal errors from agent loading + Errors []string `json:"errors"` } -// Start position of the selection -type Start struct { - // Start character offset within the line (0-based) - Character float64 `json:"character"` - // Start line number (0-based) - Line float64 `json:"line"` +func (*SessionCustomAgentsUpdatedData) sessionEventData() {} + +// SessionMcpServersLoadedData holds the payload for session.mcp_servers_loaded events. +type SessionMcpServersLoadedData struct { + // Array of MCP server status summaries + Servers []SessionMcpServersLoadedDataServersItem `json:"servers"` } -// Background tasks still running when the agent became idle -type BackgroundTasks struct { - // Currently running background agents - Agents []BackgroundTasksAgent `json:"agents"` - // Currently running background shell commands - Shells []Shell `json:"shells"` +func (*SessionMcpServersLoadedData) sessionEventData() {} + +// SessionMcpServerStatusChangedData holds the payload for session.mcp_server_status_changed events. +type SessionMcpServerStatusChangedData struct { + // Name of the MCP server whose status changed + ServerName string `json:"serverName"` + // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status SessionMcpServersLoadedDataServersItemStatus `json:"status"` } -// A background agent task -type BackgroundTasksAgent struct { - // Unique identifier of the background agent - AgentID string `json:"agentId"` - // Type of the background agent - AgentType string `json:"agentType"` - // Human-readable description of the agent task - Description *string `json:"description,omitempty"` +func (*SessionMcpServerStatusChangedData) sessionEventData() {} + +// SessionExtensionsLoadedData holds the payload for session.extensions_loaded events. +type SessionExtensionsLoadedData struct { + // Array of discovered extensions and their status + Extensions []SessionExtensionsLoadedDataExtensionsItem `json:"extensions"` } -// A background shell command -type Shell struct { - // Human-readable description of the shell command - Description *string `json:"description,omitempty"` - // Unique identifier of the background shell - ShellID string `json:"shellId"` +func (*SessionExtensionsLoadedData) sessionEventData() {} + +// Working directory and git context at session start +type SessionStartDataContext struct { + // Current working directory path + Cwd string `json:"cwd"` + // Root directory of the git repository, resolved via git rev-parse + GitRoot *string `json:"gitRoot,omitempty"` + // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + Repository *string `json:"repository,omitempty"` + // Hosting platform type of the repository (github or ado) + HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + // Current git branch name + Branch *string `json:"branch,omitempty"` + // Head commit of current git branch at session start time + HeadCommit *string `json:"headCommit,omitempty"` + // Base commit of current git branch at session start time + BaseCommit *string `json:"baseCommit,omitempty"` +} + +// Updated working directory and git context at resume time +type SessionResumeDataContext struct { + // Current working directory path + Cwd string `json:"cwd"` + // Root directory of the git repository, resolved via git rev-parse + GitRoot *string `json:"gitRoot,omitempty"` + // Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + Repository *string `json:"repository,omitempty"` + // Hosting platform type of the repository (github or ado) + HostType *SessionStartDataContextHostType `json:"hostType,omitempty"` + // Current git branch name + Branch *string `json:"branch,omitempty"` + // Head commit of current git branch at session start time + HeadCommit *string `json:"headCommit,omitempty"` + // Base commit of current git branch at session start time + BaseCommit *string `json:"baseCommit,omitempty"` +} + +// Repository context for the handed-off session +type SessionHandoffDataRepository struct { + // Repository owner (user or organization) + Owner string `json:"owner"` + // Repository name + Name string `json:"name"` + // Git branch name, if applicable + Branch *string `json:"branch,omitempty"` } // Aggregate code change metrics for the session -type CodeChanges struct { - // List of file paths that were modified during the session - FilesModified []string `json:"filesModified"` +type SessionShutdownDataCodeChanges struct { // Total number of lines added during the session LinesAdded float64 `json:"linesAdded"` // Total number of lines removed during the session LinesRemoved float64 `json:"linesRemoved"` + // List of file paths that were modified during the session + FilesModified []string `json:"filesModified"` } -type DataCommand struct { - Description *string `json:"description,omitempty"` - Name string `json:"name"` +// Request count and cost metrics +type SessionShutdownDataModelMetricsValueRequests struct { + // Total number of API requests made to this model + Count float64 `json:"count"` + // Cumulative cost multiplier for requests to this model + Cost float64 `json:"cost"` +} + +// Token usage breakdown +type SessionShutdownDataModelMetricsValueUsage struct { + // Total input tokens consumed across all requests to this model + InputTokens float64 `json:"inputTokens"` + // Total output tokens produced across all requests to this model + OutputTokens float64 `json:"outputTokens"` + // Total tokens read from prompt cache across all requests + CacheReadTokens float64 `json:"cacheReadTokens"` + // Total tokens written to prompt cache across all requests + CacheWriteTokens float64 `json:"cacheWriteTokens"` +} + +type SessionShutdownDataModelMetricsValue struct { + // Request count and cost metrics + Requests SessionShutdownDataModelMetricsValueRequests `json:"requests"` + // Token usage breakdown + Usage SessionShutdownDataModelMetricsValueUsage `json:"usage"` } // Token usage breakdown for the compaction LLM call -type CompactionTokensUsed struct { - // Cached input tokens reused in the compaction LLM call - CachedInput float64 `json:"cachedInput"` +type SessionCompactionCompleteDataCompactionTokensUsed struct { // Input tokens consumed by the compaction LLM call Input float64 `json:"input"` // Output tokens produced by the compaction LLM call Output float64 `json:"output"` + // Cached input tokens reused in the compaction LLM call + CachedInput float64 `json:"cachedInput"` +} + +// Optional line range to scope the attachment to a specific section of the file +type UserMessageDataAttachmentsItemLineRange struct { + // Start line number (1-based) + Start float64 `json:"start"` + // End line number (1-based, inclusive) + End float64 `json:"end"` +} + +// Start position of the selection +type UserMessageDataAttachmentsItemSelectionStart struct { + // Start line number (0-based) + Line float64 `json:"line"` + // Start character offset within the line (0-based) + Character float64 `json:"character"` +} + +// End position of the selection +type UserMessageDataAttachmentsItemSelectionEnd struct { + // End line number (0-based) + Line float64 `json:"line"` + // End character offset within the line (0-based) + Character float64 `json:"character"` +} + +// Position range of the selection within the file +type UserMessageDataAttachmentsItemSelection struct { + // Start position of the selection + Start UserMessageDataAttachmentsItemSelectionStart `json:"start"` + // End position of the selection + End UserMessageDataAttachmentsItemSelectionEnd `json:"end"` +} + +// A user message attachment — a file, directory, code selection, blob, or GitHub reference +type UserMessageDataAttachmentsItem struct { + // Type discriminator + Type UserMessageDataAttachmentsItemType `json:"type"` + // Absolute file path + Path *string `json:"path,omitempty"` + // User-facing display name for the attachment + DisplayName *string `json:"displayName,omitempty"` + // Optional line range to scope the attachment to a specific section of the file + LineRange *UserMessageDataAttachmentsItemLineRange `json:"lineRange,omitempty"` + // Absolute path to the file containing the selection + FilePath *string `json:"filePath,omitempty"` + // The selected text content + Text *string `json:"text,omitempty"` + // Position range of the selection within the file + Selection *UserMessageDataAttachmentsItemSelection `json:"selection,omitempty"` + // Issue, pull request, or discussion number + Number *float64 `json:"number,omitempty"` + // Title of the referenced item + Title *string `json:"title,omitempty"` + // Type of GitHub reference + ReferenceType *UserMessageDataAttachmentsItemReferenceType `json:"referenceType,omitempty"` + // Current state of the referenced item (e.g., open, closed, merged) + State *string `json:"state,omitempty"` + // URL to the referenced item on GitHub + URL *string `json:"url,omitempty"` + // Base64-encoded content + Data *string `json:"data,omitempty"` + // MIME type of the inline data + MIMEType *string `json:"mimeType,omitempty"` } -// Working directory and git context at session start -// -// Updated working directory and git context at resume time -type ContextClass struct { - // Base commit of current git branch at session start time - BaseCommit *string `json:"baseCommit,omitempty"` - // Current git branch name - Branch *string `json:"branch,omitempty"` - // Current working directory path - Cwd string `json:"cwd"` - // Root directory of the git repository, resolved via git rev-parse - GitRoot *string `json:"gitRoot,omitempty"` - // Head commit of current git branch at session start time - HeadCommit *string `json:"headCommit,omitempty"` - // Hosting platform type of the repository (github or ado) - HostType *HostType `json:"hostType,omitempty"` - // Repository identifier derived from the git remote URL ("owner/name" for GitHub, - // "org/project/repo" for Azure DevOps) - Repository *string `json:"repository,omitempty"` +// A tool invocation request from the assistant +type AssistantMessageDataToolRequestsItem struct { + // Unique identifier for this tool call + ToolCallID string `json:"toolCallId"` + // Name of the tool being invoked + Name string `json:"name"` + // Arguments to pass to the tool, format depends on the tool + Arguments any `json:"arguments,omitempty"` + // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + Type *AssistantMessageDataToolRequestsItemType `json:"type,omitempty"` + // Human-readable display title for the tool + ToolTitle *string `json:"toolTitle,omitempty"` + // Name of the MCP server hosting this tool, when the tool is an MCP tool + McpServerName *string `json:"mcpServerName,omitempty"` + // Resolved intention summary describing what this specific call does + IntentionSummary *string `json:"intentionSummary,omitempty"` } -// Per-request cost and usage data from the CAPI copilot_usage response field -type CopilotUsage struct { - // Itemized token usage breakdown - TokenDetails []TokenDetail `json:"tokenDetails"` - // Total cost in nano-AIU (AI Units) for this request - TotalNanoAiu float64 `json:"totalNanoAiu"` +type AssistantUsageDataQuotaSnapshotsValue struct { + // Whether the user has an unlimited usage entitlement + IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` + // Total requests allowed by the entitlement + EntitlementRequests float64 `json:"entitlementRequests"` + // Number of requests already consumed + UsedRequests float64 `json:"usedRequests"` + // Whether usage is still permitted after quota exhaustion + UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` + // Number of requests over the entitlement limit + Overage float64 `json:"overage"` + // Whether overage is allowed when quota is exhausted + OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` + // Percentage of quota remaining (0.0 to 1.0) + RemainingPercentage float64 `json:"remainingPercentage"` + // Date when the quota resets + ResetDate *time.Time `json:"resetDate,omitempty"` } // Token usage detail for a single billing category -type TokenDetail struct { +type AssistantUsageDataCopilotUsageTokenDetailsItem struct { // Number of tokens in this billing batch BatchSize float64 `json:"batchSize"` // Cost per batch of tokens @@ -1056,838 +1833,490 @@ type TokenDetail struct { TokenType string `json:"tokenType"` } +// Per-request cost and usage data from the CAPI copilot_usage response field +type AssistantUsageDataCopilotUsage struct { + // Itemized token usage breakdown + TokenDetails []AssistantUsageDataCopilotUsageTokenDetailsItem `json:"tokenDetails"` + // Total cost in nano-AIU (AI Units) for this request + TotalNanoAiu float64 `json:"totalNanoAiu"` +} + +// Icon image for a resource +type ToolExecutionCompleteDataResultContentsItemIconsItem struct { + // URL or path to the icon image + Src string `json:"src"` + // MIME type of the icon image + MIMEType *string `json:"mimeType,omitempty"` + // Available icon sizes (e.g., ['16x16', '32x32']) + Sizes []string `json:"sizes,omitempty"` + // Theme variant this icon is intended for + Theme *ToolExecutionCompleteDataResultContentsItemIconsItemTheme `json:"theme,omitempty"` +} + +// A content block within a tool result, which may be text, terminal output, image, audio, or a resource +type ToolExecutionCompleteDataResultContentsItem struct { + // Type discriminator + Type ToolExecutionCompleteDataResultContentsItemType `json:"type"` + // The text content + Text *string `json:"text,omitempty"` + // Process exit code, if the command has completed + ExitCode *float64 `json:"exitCode,omitempty"` + // Working directory where the command was executed + Cwd *string `json:"cwd,omitempty"` + // Base64-encoded image data + Data *string `json:"data,omitempty"` + // MIME type of the image (e.g., image/png, image/jpeg) + MIMEType *string `json:"mimeType,omitempty"` + // Icons associated with this resource + Icons []ToolExecutionCompleteDataResultContentsItemIconsItem `json:"icons,omitempty"` + // Resource name identifier + Name *string `json:"name,omitempty"` + // Human-readable display title for the resource + Title *string `json:"title,omitempty"` + // URI identifying the resource + URI *string `json:"uri,omitempty"` + // Human-readable description of the resource + Description *string `json:"description,omitempty"` + // Size of the resource in bytes + Size *float64 `json:"size,omitempty"` + // The embedded resource contents, either text or base64-encoded binary + Resource any `json:"resource,omitempty"` +} + +// Tool execution result on success +type ToolExecutionCompleteDataResult struct { + // Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency + Content string `json:"content"` + // Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. + DetailedContent *string `json:"detailedContent,omitempty"` + // Structured content blocks (text, images, audio, resources) returned by the tool in their native format + Contents []ToolExecutionCompleteDataResultContentsItem `json:"contents,omitempty"` +} + // Error details when the tool execution failed -// -// Error details when the hook failed -type ErrorClass struct { +type ToolExecutionCompleteDataError struct { + // Human-readable error message + Message string `json:"message"` // Machine-readable error code Code *string `json:"code,omitempty"` +} + +// Error details when the hook failed +type HookEndDataError struct { // Human-readable error message Message string `json:"message"` // Error stack trace, when available Stack *string `json:"stack,omitempty"` } -type Extension struct { - // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') - ID string `json:"id"` - // Extension name (directory name) - Name string `json:"name"` - // Discovery source - Source Source `json:"source"` - // Current status: running, disabled, failed, or starting - Status ExtensionStatus `json:"status"` +// Metadata about the prompt template and its construction +type SystemMessageDataMetadata struct { + // Version identifier of the prompt template used + PromptVersion *string `json:"promptVersion,omitempty"` + // Template variables used when constructing the prompt + Variables map[string]any `json:"variables,omitempty"` } // Structured metadata identifying what triggered this notification -type KindClass struct { +type SystemNotificationDataKind struct { + // Type discriminator + Type SystemNotificationDataKindType `json:"type"` // Unique identifier of the background agent AgentID *string `json:"agentId,omitempty"` // Type of the agent (e.g., explore, task, general-purpose) AgentType *string `json:"agentType,omitempty"` + // Whether the agent completed successfully or failed + Status *SystemNotificationDataKindStatus `json:"status,omitempty"` // Human-readable description of the agent task - // - // Human-readable description of the command Description *string `json:"description,omitempty"` // The full prompt given to the background agent Prompt *string `json:"prompt,omitempty"` - // Whether the agent completed successfully or failed - Status *KindStatus `json:"status,omitempty"` - Type KindType `json:"type"` - // Exit code of the shell command, if available - ExitCode *float64 `json:"exitCode,omitempty"` // Unique identifier of the shell session - // - // Unique identifier of the detached shell session ShellID *string `json:"shellId,omitempty"` + // Exit code of the shell command, if available + ExitCode *float64 `json:"exitCode,omitempty"` } -// Metadata about the prompt template and its construction -type Metadata struct { - // Version identifier of the prompt template used - PromptVersion *string `json:"promptVersion,omitempty"` - // Template variables used when constructing the prompt - Variables map[string]interface{} `json:"variables,omitempty"` -} - -type ModelMetric struct { - // Request count and cost metrics - Requests Requests `json:"requests"` - // Token usage breakdown - Usage Usage `json:"usage"` -} - -// Request count and cost metrics -type Requests struct { - // Cumulative cost multiplier for requests to this model - Cost float64 `json:"cost"` - // Total number of API requests made to this model - Count float64 `json:"count"` +type PermissionRequestedDataPermissionRequestCommandsItem struct { + // Command identifier (e.g., executable name) + Identifier string `json:"identifier"` + // Whether this command is read-only (no side effects) + ReadOnly bool `json:"readOnly"` } -// Token usage breakdown -type Usage struct { - // Total tokens read from prompt cache across all requests - CacheReadTokens float64 `json:"cacheReadTokens"` - // Total tokens written to prompt cache across all requests - CacheWriteTokens float64 `json:"cacheWriteTokens"` - // Total input tokens consumed across all requests to this model - InputTokens float64 `json:"inputTokens"` - // Total output tokens produced across all requests to this model - OutputTokens float64 `json:"outputTokens"` +type PermissionRequestedDataPermissionRequestPossibleUrlsItem struct { + // URL that may be accessed by the command + URL string `json:"url"` } // Details of the permission being requested -// -// # Shell command permission request -// -// # File write permission request -// -// # File or directory read permission request -// -// # MCP tool invocation permission request -// -// # URL access permission request -// -// # Memory storage permission request -// -// # Custom tool invocation permission request -// -// Hook confirmation permission request -type PermissionRequest struct { - // Whether the UI can offer session-wide approval for this command pattern - CanOfferSessionApproval *bool `json:"canOfferSessionApproval,omitempty"` - // Parsed command identifiers found in the command text - Commands []PermissionRequestCommand `json:"commands,omitempty"` +type PermissionRequestedDataPermissionRequest struct { + // Kind discriminator + Kind PermissionRequestedDataPermissionRequestKind `json:"kind"` + // Tool call ID that triggered this permission request + ToolCallID *string `json:"toolCallId,omitempty"` // The complete shell command text to be executed FullCommandText *string `json:"fullCommandText,omitempty"` - // Whether the command includes a file write redirection (e.g., > or >>) - HasWriteFileRedirection *bool `json:"hasWriteFileRedirection,omitempty"` // Human-readable description of what the command intends to do - // - // Human-readable description of the intended file change - // - // Human-readable description of why the file is being read - // - // Human-readable description of why the URL is being accessed Intention *string `json:"intention,omitempty"` - // Permission kind discriminator - Kind PermissionRequestKind `json:"kind"` + // Parsed command identifiers found in the command text + Commands []PermissionRequestedDataPermissionRequestCommandsItem `json:"commands,omitempty"` // File paths that may be read or written by the command PossiblePaths []string `json:"possiblePaths,omitempty"` // URLs that may be accessed by the command - PossibleUrls []PossibleURL `json:"possibleUrls,omitempty"` - // Tool call ID that triggered this permission request - ToolCallID *string `json:"toolCallId,omitempty"` + PossibleUrls []PermissionRequestedDataPermissionRequestPossibleUrlsItem `json:"possibleUrls,omitempty"` + // Whether the command includes a file write redirection (e.g., > or >>) + HasWriteFileRedirection *bool `json:"hasWriteFileRedirection,omitempty"` + // Whether the UI can offer session-wide approval for this command pattern + CanOfferSessionApproval *bool `json:"canOfferSessionApproval,omitempty"` // Optional warning message about risks of running this command Warning *string `json:"warning,omitempty"` - // Unified diff showing the proposed changes - Diff *string `json:"diff,omitempty"` // Path of the file being written to FileName *string `json:"fileName,omitempty"` + // Unified diff showing the proposed changes + Diff *string `json:"diff,omitempty"` // Complete new file contents for newly created files NewFileContents *string `json:"newFileContents,omitempty"` // Path of the file or directory being read Path *string `json:"path,omitempty"` - // Arguments to pass to the MCP tool - // - // Arguments to pass to the custom tool - Args interface{} `json:"args"` - // Whether this MCP tool is read-only (no side effects) - ReadOnly *bool `json:"readOnly,omitempty"` // Name of the MCP server providing the tool ServerName *string `json:"serverName,omitempty"` // Internal name of the MCP tool - // - // Name of the custom tool - // - // Name of the tool the hook is gating ToolName *string `json:"toolName,omitempty"` // Human-readable title of the MCP tool ToolTitle *string `json:"toolTitle,omitempty"` + // Arguments to pass to the MCP tool + Args any `json:"args,omitempty"` + // Whether this MCP tool is read-only (no side effects) + ReadOnly *bool `json:"readOnly,omitempty"` // URL to be fetched URL *string `json:"url,omitempty"` - // Source references for the stored fact - Citations *string `json:"citations,omitempty"` - // The fact or convention being stored - Fact *string `json:"fact,omitempty"` // Topic or subject of the memory being stored Subject *string `json:"subject,omitempty"` + // The fact or convention being stored + Fact *string `json:"fact,omitempty"` + // Source references for the stored fact + Citations *string `json:"citations,omitempty"` // Description of what the custom tool does ToolDescription *string `json:"toolDescription,omitempty"` + // Arguments of the tool call being gated + ToolArgs any `json:"toolArgs,omitempty"` // Optional message from the hook explaining why confirmation is needed HookMessage *string `json:"hookMessage,omitempty"` - // Arguments of the tool call being gated - ToolArgs interface{} `json:"toolArgs"` -} - -type PermissionRequestCommand struct { - // Command identifier (e.g., executable name) - Identifier string `json:"identifier"` - // Whether this command is read-only (no side effects) - ReadOnly bool `json:"readOnly"` -} - -type PossibleURL struct { - // URL that may be accessed by the command - URL string `json:"url"` -} - -type QuotaSnapshot struct { - // Total requests allowed by the entitlement - EntitlementRequests float64 `json:"entitlementRequests"` - // Whether the user has an unlimited usage entitlement - IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` - // Number of requests over the entitlement limit - Overage float64 `json:"overage"` - // Whether overage is allowed when quota is exhausted - OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` - // Percentage of quota remaining (0.0 to 1.0) - RemainingPercentage float64 `json:"remainingPercentage"` - // Date when the quota resets - ResetDate *time.Time `json:"resetDate,omitempty"` - // Whether usage is still permitted after quota exhaustion - UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` - // Number of requests already consumed - UsedRequests float64 `json:"usedRequests"` -} - -// Repository context for the handed-off session -type RepositoryClass struct { - // Git branch name, if applicable - Branch *string `json:"branch,omitempty"` - // Repository name - Name string `json:"name"` - // Repository owner (user or organization) - Owner string `json:"owner"` } -// JSON Schema describing the form fields to present to the user (form mode only) -type RequestedSchema struct { - // Form field definitions, keyed by field name - Properties map[string]interface{} `json:"properties"` - // List of required field names - Required []string `json:"required,omitempty"` - // Schema type indicator (always 'object') - Type RequestedSchemaType `json:"type"` -} - -// Tool execution result on success -// // The result of the permission request -type Result struct { - // Concise tool result text sent to the LLM for chat completion, potentially truncated for - // token efficiency - Content *string `json:"content,omitempty"` - // Structured content blocks (text, images, audio, resources) returned by the tool in their - // native format - Contents []Content `json:"contents,omitempty"` - // Full detailed tool result for UI/timeline display, preserving complete content such as - // diffs. Falls back to content when absent. - DetailedContent *string `json:"detailedContent,omitempty"` +type PermissionCompletedDataResult struct { // The outcome of the permission request - Kind *ResultKind `json:"kind,omitempty"` -} - -// A content block within a tool result, which may be text, terminal output, image, audio, -// or a resource -// -// # Plain text content block -// -// Terminal/shell output content block with optional exit code and working directory -// -// # Image content block with base64-encoded data -// -// # Audio content block with base64-encoded data -// -// # Resource link content block referencing an external resource -// -// Embedded resource content block with inline text or binary data -type Content struct { - // The text content - // - // Terminal/shell output text - Text *string `json:"text,omitempty"` - // Content block type discriminator - Type ContentType `json:"type"` - // Working directory where the command was executed - Cwd *string `json:"cwd,omitempty"` - // Process exit code, if the command has completed - ExitCode *float64 `json:"exitCode,omitempty"` - // Base64-encoded image data - // - // Base64-encoded audio data - Data *string `json:"data,omitempty"` - // MIME type of the image (e.g., image/png, image/jpeg) - // - // MIME type of the audio (e.g., audio/wav, audio/mpeg) - // - // MIME type of the resource content - MIMEType *string `json:"mimeType,omitempty"` - // Human-readable description of the resource - Description *string `json:"description,omitempty"` - // Icons associated with this resource - Icons []Icon `json:"icons,omitempty"` - // Resource name identifier - Name *string `json:"name,omitempty"` - // Size of the resource in bytes - Size *float64 `json:"size,omitempty"` - // Human-readable display title for the resource - Title *string `json:"title,omitempty"` - // URI identifying the resource - URI *string `json:"uri,omitempty"` - // The embedded resource contents, either text or base64-encoded binary - Resource *ResourceClass `json:"resource,omitempty"` + Kind PermissionCompletedDataResultKind `json:"kind"` +} + +// JSON Schema describing the form fields to present to the user (form mode only) +type ElicitationRequestedDataRequestedSchema struct { + // Schema type indicator (always 'object') + Type string `json:"type"` + // Form field definitions, keyed by field name + Properties map[string]any `json:"properties"` + // List of required field names + Required []string `json:"required,omitempty"` } -// Icon image for a resource -type Icon struct { - // MIME type of the icon image - MIMEType *string `json:"mimeType,omitempty"` - // Available icon sizes (e.g., ['16x16', '32x32']) - Sizes []string `json:"sizes,omitempty"` - // URL or path to the icon image - Src string `json:"src"` - // Theme variant this icon is intended for - Theme *Theme `json:"theme,omitempty"` +// Static OAuth client configuration, if the server specifies one +type McpOauthRequiredDataStaticClientConfig struct { + // OAuth client ID for the server + ClientID string `json:"clientId"` + // Whether this is a public OAuth client + PublicClient *bool `json:"publicClient,omitempty"` } -// The embedded resource contents, either text or base64-encoded binary -type ResourceClass struct { - // MIME type of the text content - // - // MIME type of the blob content - MIMEType *string `json:"mimeType,omitempty"` - // Text content of the resource - Text *string `json:"text,omitempty"` - // URI identifying the resource - URI string `json:"uri"` - // Base64-encoded binary content of the resource - Blob *string `json:"blob,omitempty"` +type CommandsChangedDataCommandsItem struct { + Name string `json:"name"` + Description *string `json:"description,omitempty"` } -type Server struct { - // Error message if the server failed to connect - Error *string `json:"error,omitempty"` - // Server name (config key) - Name string `json:"name"` - // Configuration source: user, workspace, plugin, or builtin - Source *string `json:"source,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status ServerStatus `json:"status"` +// UI capability changes +type CapabilitiesChangedDataUI struct { + // Whether elicitation is now supported + Elicitation *bool `json:"elicitation,omitempty"` } -type Skill struct { - // Description of what the skill does - Description string `json:"description"` - // Whether the skill is currently enabled - Enabled bool `json:"enabled"` +type SessionSkillsLoadedDataSkillsItem struct { // Unique identifier for the skill Name string `json:"name"` - // Absolute path to the skill file, if available - Path *string `json:"path,omitempty"` + // Description of what the skill does + Description string `json:"description"` // Source location type of the skill (e.g., project, personal, plugin) Source string `json:"source"` // Whether the skill can be invoked by the user as a slash command UserInvocable bool `json:"userInvocable"` + // Whether the skill is currently enabled + Enabled bool `json:"enabled"` + // Absolute path to the skill file, if available + Path *string `json:"path,omitempty"` } -// Static OAuth client configuration, if the server specifies one -type StaticClientConfig struct { - // OAuth client ID for the server - ClientID string `json:"clientId"` - // Whether this is a public OAuth client - PublicClient *bool `json:"publicClient,omitempty"` +type SessionCustomAgentsUpdatedDataAgentsItem struct { + // Unique identifier for the agent + ID string `json:"id"` + // Internal name of the agent + Name string `json:"name"` + // Human-readable display name + DisplayName string `json:"displayName"` + // Description of what the agent does + Description string `json:"description"` + // Source location: user, project, inherited, remote, or plugin + Source string `json:"source"` + // List of tool names available to this agent + Tools []string `json:"tools"` + // Whether the agent can be selected by the user + UserInvocable bool `json:"userInvocable"` + // Model override for this agent, if set + Model *string `json:"model,omitempty"` } -// A tool invocation request from the assistant -type ToolRequest struct { - // Arguments to pass to the tool, format depends on the tool - Arguments interface{} `json:"arguments"` - // Resolved intention summary describing what this specific call does - IntentionSummary *string `json:"intentionSummary"` - // Name of the MCP server hosting this tool, when the tool is an MCP tool - MCPServerName *string `json:"mcpServerName,omitempty"` - // Name of the tool being invoked +type SessionMcpServersLoadedDataServersItem struct { + // Server name (config key) Name string `json:"name"` - // Unique identifier for this tool call - ToolCallID string `json:"toolCallId"` - // Human-readable display title for the tool - ToolTitle *string `json:"toolTitle,omitempty"` - // Tool call type: "function" for standard tool calls, "custom" for grammar-based tool - // calls. Defaults to "function" when absent. - Type *ToolRequestType `json:"type,omitempty"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status SessionMcpServersLoadedDataServersItemStatus `json:"status"` + // Configuration source: user, workspace, plugin, or builtin + Source *string `json:"source,omitempty"` + // Error message if the server failed to connect + Error *string `json:"error,omitempty"` } -// UI capability changes -type UI struct { - // Whether elicitation is now supported - Elicitation *bool `json:"elicitation,omitempty"` +type SessionExtensionsLoadedDataExtensionsItem struct { + // Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') + ID string `json:"id"` + // Extension name (directory name) + Name string `json:"name"` + // Discovery source + Source SessionExtensionsLoadedDataExtensionsItemSource `json:"source"` + // Current status: running, disabled, failed, or starting + Status SessionExtensionsLoadedDataExtensionsItemStatus `json:"status"` } -// The agent mode that was active when this message was sent -type AgentMode string +// Hosting platform type of the repository (github or ado) +type SessionStartDataContextHostType string const ( - AgentModeShell AgentMode = "shell" - AgentModeAutopilot AgentMode = "autopilot" - AgentModeInteractive AgentMode = "interactive" - AgentModePlan AgentMode = "plan" + SessionStartDataContextHostTypeGithub SessionStartDataContextHostType = "github" + SessionStartDataContextHostTypeAdo SessionStartDataContextHostType = "ado" ) -// Type of GitHub reference -type ReferenceType string +// The type of operation performed on the plan file +type SessionPlanChangedDataOperation string const ( - ReferenceTypeDiscussion ReferenceType = "discussion" - ReferenceTypeIssue ReferenceType = "issue" - ReferenceTypePr ReferenceType = "pr" + SessionPlanChangedDataOperationCreate SessionPlanChangedDataOperation = "create" + SessionPlanChangedDataOperationUpdate SessionPlanChangedDataOperation = "update" + SessionPlanChangedDataOperationDelete SessionPlanChangedDataOperation = "delete" ) -type AttachmentType string +// Whether the file was newly created or updated +type SessionWorkspaceFileChangedDataOperation string const ( - AttachmentTypeBlob AttachmentType = "blob" - AttachmentTypeDirectory AttachmentType = "directory" - AttachmentTypeFile AttachmentType = "file" - AttachmentTypeGithubReference AttachmentType = "github_reference" - AttachmentTypeSelection AttachmentType = "selection" + SessionWorkspaceFileChangedDataOperationCreate SessionWorkspaceFileChangedDataOperation = "create" + SessionWorkspaceFileChangedDataOperationUpdate SessionWorkspaceFileChangedDataOperation = "update" ) -// Hosting platform type of the repository (github or ado) -type HostType string +// Origin type of the session being handed off +type SessionHandoffDataSourceType string const ( - HostTypeAdo HostType = "ado" - HostTypeGithub HostType = "github" + SessionHandoffDataSourceTypeRemote SessionHandoffDataSourceType = "remote" + SessionHandoffDataSourceTypeLocal SessionHandoffDataSourceType = "local" ) -// Discovery source -type Source string +// Whether the session ended normally ("routine") or due to a crash/fatal error ("error") +type SessionShutdownDataShutdownType string const ( - SourceProject Source = "project" - SourceUser Source = "user" + SessionShutdownDataShutdownTypeRoutine SessionShutdownDataShutdownType = "routine" + SessionShutdownDataShutdownTypeError SessionShutdownDataShutdownType = "error" ) -// Current status: running, disabled, failed, or starting -type ExtensionStatus string +// Type discriminator for UserMessageDataAttachmentsItem. +type UserMessageDataAttachmentsItemType string const ( - ExtensionStatusDisabled ExtensionStatus = "disabled" - ExtensionStatusFailed ExtensionStatus = "failed" - ExtensionStatusRunning ExtensionStatus = "running" - ExtensionStatusStarting ExtensionStatus = "starting" + UserMessageDataAttachmentsItemTypeFile UserMessageDataAttachmentsItemType = "file" + UserMessageDataAttachmentsItemTypeDirectory UserMessageDataAttachmentsItemType = "directory" + UserMessageDataAttachmentsItemTypeSelection UserMessageDataAttachmentsItemType = "selection" + UserMessageDataAttachmentsItemTypeGithubReference UserMessageDataAttachmentsItemType = "github_reference" + UserMessageDataAttachmentsItemTypeBlob UserMessageDataAttachmentsItemType = "blob" ) -// Whether the agent completed successfully or failed -type KindStatus string +// Type of GitHub reference +type UserMessageDataAttachmentsItemReferenceType string const ( - KindStatusCompleted KindStatus = "completed" - KindStatusFailed KindStatus = "failed" + UserMessageDataAttachmentsItemReferenceTypeIssue UserMessageDataAttachmentsItemReferenceType = "issue" + UserMessageDataAttachmentsItemReferenceTypePr UserMessageDataAttachmentsItemReferenceType = "pr" + UserMessageDataAttachmentsItemReferenceTypeDiscussion UserMessageDataAttachmentsItemReferenceType = "discussion" ) -type KindType string +// The agent mode that was active when this message was sent +type UserMessageDataAgentMode string const ( - KindTypeAgentCompleted KindType = "agent_completed" - KindTypeAgentIdle KindType = "agent_idle" - KindTypeShellCompleted KindType = "shell_completed" - KindTypeShellDetachedCompleted KindType = "shell_detached_completed" + UserMessageDataAgentModeInteractive UserMessageDataAgentMode = "interactive" + UserMessageDataAgentModePlan UserMessageDataAgentMode = "plan" + UserMessageDataAgentModeAutopilot UserMessageDataAgentMode = "autopilot" + UserMessageDataAgentModeShell UserMessageDataAgentMode = "shell" ) -// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to -// "form" when absent. -type Mode string +// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. +type AssistantMessageDataToolRequestsItemType string const ( - ModeForm Mode = "form" - ModeURL Mode = "url" + AssistantMessageDataToolRequestsItemTypeFunction AssistantMessageDataToolRequestsItemType = "function" + AssistantMessageDataToolRequestsItemTypeCustom AssistantMessageDataToolRequestsItemType = "custom" ) -// The type of operation performed on the plan file -// -// Whether the file was newly created or updated -type Operation string +// Type discriminator for ToolExecutionCompleteDataResultContentsItem. +type ToolExecutionCompleteDataResultContentsItemType string const ( - OperationCreate Operation = "create" - OperationDelete Operation = "delete" - OperationUpdate Operation = "update" + ToolExecutionCompleteDataResultContentsItemTypeText ToolExecutionCompleteDataResultContentsItemType = "text" + ToolExecutionCompleteDataResultContentsItemTypeTerminal ToolExecutionCompleteDataResultContentsItemType = "terminal" + ToolExecutionCompleteDataResultContentsItemTypeImage ToolExecutionCompleteDataResultContentsItemType = "image" + ToolExecutionCompleteDataResultContentsItemTypeAudio ToolExecutionCompleteDataResultContentsItemType = "audio" + ToolExecutionCompleteDataResultContentsItemTypeResourceLink ToolExecutionCompleteDataResultContentsItemType = "resource_link" + ToolExecutionCompleteDataResultContentsItemTypeResource ToolExecutionCompleteDataResultContentsItemType = "resource" ) -type PermissionRequestKind string +// Theme variant this icon is intended for +type ToolExecutionCompleteDataResultContentsItemIconsItemTheme string const ( - PermissionRequestKindCustomTool PermissionRequestKind = "custom-tool" - PermissionRequestKindHook PermissionRequestKind = "hook" - PermissionRequestKindShell PermissionRequestKind = "shell" - PermissionRequestKindURL PermissionRequestKind = "url" - PermissionRequestKindMcp PermissionRequestKind = "mcp" - PermissionRequestKindMemory PermissionRequestKind = "memory" - PermissionRequestKindRead PermissionRequestKind = "read" - PermissionRequestKindWrite PermissionRequestKind = "write" + ToolExecutionCompleteDataResultContentsItemIconsItemThemeLight ToolExecutionCompleteDataResultContentsItemIconsItemTheme = "light" + ToolExecutionCompleteDataResultContentsItemIconsItemThemeDark ToolExecutionCompleteDataResultContentsItemIconsItemTheme = "dark" ) -type RequestedSchemaType string +// Message role: "system" for system prompts, "developer" for developer-injected instructions +type SystemMessageDataRole string const ( - RequestedSchemaTypeObject RequestedSchemaType = "object" + SystemMessageDataRoleSystem SystemMessageDataRole = "system" + SystemMessageDataRoleDeveloper SystemMessageDataRole = "developer" ) -// Theme variant this icon is intended for -type Theme string +// Type discriminator for SystemNotificationDataKind. +type SystemNotificationDataKindType string const ( - ThemeDark Theme = "dark" - ThemeLight Theme = "light" + SystemNotificationDataKindTypeAgentCompleted SystemNotificationDataKindType = "agent_completed" + SystemNotificationDataKindTypeAgentIdle SystemNotificationDataKindType = "agent_idle" + SystemNotificationDataKindTypeShellCompleted SystemNotificationDataKindType = "shell_completed" + SystemNotificationDataKindTypeShellDetachedCompleted SystemNotificationDataKindType = "shell_detached_completed" ) -type ContentType string +// Whether the agent completed successfully or failed +type SystemNotificationDataKindStatus string const ( - ContentTypeAudio ContentType = "audio" - ContentTypeImage ContentType = "image" - ContentTypeResource ContentType = "resource" - ContentTypeResourceLink ContentType = "resource_link" - ContentTypeTerminal ContentType = "terminal" - ContentTypeText ContentType = "text" + SystemNotificationDataKindStatusCompleted SystemNotificationDataKindStatus = "completed" + SystemNotificationDataKindStatusFailed SystemNotificationDataKindStatus = "failed" ) -// The outcome of the permission request -type ResultKind string +// Kind discriminator for PermissionRequestedDataPermissionRequest. +type PermissionRequestedDataPermissionRequestKind string const ( - ResultKindApproved ResultKind = "approved" - ResultKindDeniedByContentExclusionPolicy ResultKind = "denied-by-content-exclusion-policy" - ResultKindDeniedByPermissionRequestHook ResultKind = "denied-by-permission-request-hook" - ResultKindDeniedByRules ResultKind = "denied-by-rules" - ResultKindDeniedInteractivelyByUser ResultKind = "denied-interactively-by-user" - ResultKindDeniedNoApprovalRuleAndCouldNotRequestFromUser ResultKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionRequestedDataPermissionRequestKindShell PermissionRequestedDataPermissionRequestKind = "shell" + PermissionRequestedDataPermissionRequestKindWrite PermissionRequestedDataPermissionRequestKind = "write" + PermissionRequestedDataPermissionRequestKindRead PermissionRequestedDataPermissionRequestKind = "read" + PermissionRequestedDataPermissionRequestKindMcp PermissionRequestedDataPermissionRequestKind = "mcp" + PermissionRequestedDataPermissionRequestKindURL PermissionRequestedDataPermissionRequestKind = "url" + PermissionRequestedDataPermissionRequestKindMemory PermissionRequestedDataPermissionRequestKind = "memory" + PermissionRequestedDataPermissionRequestKindCustomTool PermissionRequestedDataPermissionRequestKind = "custom-tool" + PermissionRequestedDataPermissionRequestKindHook PermissionRequestedDataPermissionRequestKind = "hook" ) -// Message role: "system" for system prompts, "developer" for developer-injected instructions -type Role string +// The outcome of the permission request +type PermissionCompletedDataResultKind string const ( - RoleDeveloper Role = "developer" - RoleSystem Role = "system" + PermissionCompletedDataResultKindApproved PermissionCompletedDataResultKind = "approved" + PermissionCompletedDataResultKindDeniedByRules PermissionCompletedDataResultKind = "denied-by-rules" + PermissionCompletedDataResultKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedDataResultKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionCompletedDataResultKindDeniedInteractivelyByUser PermissionCompletedDataResultKind = "denied-interactively-by-user" + PermissionCompletedDataResultKindDeniedByContentExclusionPolicy PermissionCompletedDataResultKind = "denied-by-content-exclusion-policy" + PermissionCompletedDataResultKindDeniedByPermissionRequestHook PermissionCompletedDataResultKind = "denied-by-permission-request-hook" ) -// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -// -// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type ServerStatus string +// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. +type ElicitationRequestedDataMode string const ( - ServerStatusConnected ServerStatus = "connected" - ServerStatusDisabled ServerStatus = "disabled" - ServerStatusNeedsAuth ServerStatus = "needs-auth" - ServerStatusNotConfigured ServerStatus = "not_configured" - ServerStatusPending ServerStatus = "pending" - ServerStatusFailed ServerStatus = "failed" + ElicitationRequestedDataModeForm ElicitationRequestedDataMode = "form" + ElicitationRequestedDataModeURL ElicitationRequestedDataMode = "url" ) -// Whether the session ended normally ("routine") or due to a crash/fatal error ("error") -type ShutdownType string +// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) +type ElicitationCompletedDataAction string const ( - ShutdownTypeError ShutdownType = "error" - ShutdownTypeRoutine ShutdownType = "routine" + ElicitationCompletedDataActionAccept ElicitationCompletedDataAction = "accept" + ElicitationCompletedDataActionDecline ElicitationCompletedDataAction = "decline" + ElicitationCompletedDataActionCancel ElicitationCompletedDataAction = "cancel" ) -// Origin type of the session being handed off -type SourceType string +// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +type SessionMcpServersLoadedDataServersItemStatus string const ( - SourceTypeLocal SourceType = "local" - SourceTypeRemote SourceType = "remote" + SessionMcpServersLoadedDataServersItemStatusConnected SessionMcpServersLoadedDataServersItemStatus = "connected" + SessionMcpServersLoadedDataServersItemStatusFailed SessionMcpServersLoadedDataServersItemStatus = "failed" + SessionMcpServersLoadedDataServersItemStatusNeedsAuth SessionMcpServersLoadedDataServersItemStatus = "needs-auth" + SessionMcpServersLoadedDataServersItemStatusPending SessionMcpServersLoadedDataServersItemStatus = "pending" + SessionMcpServersLoadedDataServersItemStatusDisabled SessionMcpServersLoadedDataServersItemStatus = "disabled" + SessionMcpServersLoadedDataServersItemStatusNotConfigured SessionMcpServersLoadedDataServersItemStatus = "not_configured" ) -// Tool call type: "function" for standard tool calls, "custom" for grammar-based tool -// calls. Defaults to "function" when absent. -type ToolRequestType string +// Discovery source +type SessionExtensionsLoadedDataExtensionsItemSource string const ( - ToolRequestTypeCustom ToolRequestType = "custom" - ToolRequestTypeFunction ToolRequestType = "function" + SessionExtensionsLoadedDataExtensionsItemSourceProject SessionExtensionsLoadedDataExtensionsItemSource = "project" + SessionExtensionsLoadedDataExtensionsItemSourceUser SessionExtensionsLoadedDataExtensionsItemSource = "user" ) -type SessionEventType string +// Current status: running, disabled, failed, or starting +type SessionExtensionsLoadedDataExtensionsItemStatus string const ( - SessionEventTypeAbort SessionEventType = "abort" - SessionEventTypeAssistantIntent SessionEventType = "assistant.intent" - SessionEventTypeAssistantMessage SessionEventType = "assistant.message" - SessionEventTypeAssistantMessageDelta SessionEventType = "assistant.message_delta" - SessionEventTypeAssistantReasoning SessionEventType = "assistant.reasoning" - SessionEventTypeAssistantReasoningDelta SessionEventType = "assistant.reasoning_delta" - SessionEventTypeAssistantStreamingDelta SessionEventType = "assistant.streaming_delta" - SessionEventTypeAssistantTurnEnd SessionEventType = "assistant.turn_end" - SessionEventTypeAssistantTurnStart SessionEventType = "assistant.turn_start" - SessionEventTypeAssistantUsage SessionEventType = "assistant.usage" - SessionEventTypeCapabilitiesChanged SessionEventType = "capabilities.changed" - SessionEventTypeCommandCompleted SessionEventType = "command.completed" - SessionEventTypeCommandExecute SessionEventType = "command.execute" - SessionEventTypeCommandQueued SessionEventType = "command.queued" - SessionEventTypeCommandsChanged SessionEventType = "commands.changed" - SessionEventTypeElicitationCompleted SessionEventType = "elicitation.completed" - SessionEventTypeElicitationRequested SessionEventType = "elicitation.requested" - SessionEventTypeExitPlanModeCompleted SessionEventType = "exit_plan_mode.completed" - SessionEventTypeExitPlanModeRequested SessionEventType = "exit_plan_mode.requested" - SessionEventTypeExternalToolCompleted SessionEventType = "external_tool.completed" - SessionEventTypeExternalToolRequested SessionEventType = "external_tool.requested" - SessionEventTypeHookEnd SessionEventType = "hook.end" - SessionEventTypeHookStart SessionEventType = "hook.start" - SessionEventTypeMcpOauthCompleted SessionEventType = "mcp.oauth_completed" - SessionEventTypeMcpOauthRequired SessionEventType = "mcp.oauth_required" - SessionEventTypePendingMessagesModified SessionEventType = "pending_messages.modified" - SessionEventTypePermissionCompleted SessionEventType = "permission.completed" - SessionEventTypePermissionRequested SessionEventType = "permission.requested" - SessionEventTypeSamplingCompleted SessionEventType = "sampling.completed" - SessionEventTypeSamplingRequested SessionEventType = "sampling.requested" - SessionEventTypeSessionBackgroundTasksChanged SessionEventType = "session.background_tasks_changed" - SessionEventTypeSessionCompactionComplete SessionEventType = "session.compaction_complete" - SessionEventTypeSessionCompactionStart SessionEventType = "session.compaction_start" - SessionEventTypeSessionContextChanged SessionEventType = "session.context_changed" - SessionEventTypeSessionCustomAgentsUpdated SessionEventType = "session.custom_agents_updated" - SessionEventTypeSessionError SessionEventType = "session.error" - SessionEventTypeSessionExtensionsLoaded SessionEventType = "session.extensions_loaded" - SessionEventTypeSessionHandoff SessionEventType = "session.handoff" - SessionEventTypeSessionIdle SessionEventType = "session.idle" - SessionEventTypeSessionInfo SessionEventType = "session.info" - SessionEventTypeSessionMcpServerStatusChanged SessionEventType = "session.mcp_server_status_changed" - SessionEventTypeSessionMcpServersLoaded SessionEventType = "session.mcp_servers_loaded" - SessionEventTypeSessionModeChanged SessionEventType = "session.mode_changed" - SessionEventTypeSessionModelChange SessionEventType = "session.model_change" - SessionEventTypeSessionPlanChanged SessionEventType = "session.plan_changed" - SessionEventTypeSessionRemoteSteerableChanged SessionEventType = "session.remote_steerable_changed" - SessionEventTypeSessionResume SessionEventType = "session.resume" - SessionEventTypeSessionShutdown SessionEventType = "session.shutdown" - SessionEventTypeSessionSkillsLoaded SessionEventType = "session.skills_loaded" - SessionEventTypeSessionSnapshotRewind SessionEventType = "session.snapshot_rewind" - SessionEventTypeSessionStart SessionEventType = "session.start" - SessionEventTypeSessionTaskComplete SessionEventType = "session.task_complete" - SessionEventTypeSessionTitleChanged SessionEventType = "session.title_changed" - SessionEventTypeSessionToolsUpdated SessionEventType = "session.tools_updated" - SessionEventTypeSessionTruncation SessionEventType = "session.truncation" - SessionEventTypeSessionUsageInfo SessionEventType = "session.usage_info" - SessionEventTypeSessionWarning SessionEventType = "session.warning" - SessionEventTypeSessionWorkspaceFileChanged SessionEventType = "session.workspace_file_changed" - SessionEventTypeSkillInvoked SessionEventType = "skill.invoked" - SessionEventTypeSubagentCompleted SessionEventType = "subagent.completed" - SessionEventTypeSubagentDeselected SessionEventType = "subagent.deselected" - SessionEventTypeSubagentFailed SessionEventType = "subagent.failed" - SessionEventTypeSubagentSelected SessionEventType = "subagent.selected" - SessionEventTypeSubagentStarted SessionEventType = "subagent.started" - SessionEventTypeSystemMessage SessionEventType = "system.message" - SessionEventTypeSystemNotification SessionEventType = "system.notification" - SessionEventTypeToolExecutionComplete SessionEventType = "tool.execution_complete" - SessionEventTypeToolExecutionPartialResult SessionEventType = "tool.execution_partial_result" - SessionEventTypeToolExecutionProgress SessionEventType = "tool.execution_progress" - SessionEventTypeToolExecutionStart SessionEventType = "tool.execution_start" - SessionEventTypeToolUserRequested SessionEventType = "tool.user_requested" - SessionEventTypeUserInputCompleted SessionEventType = "user_input.completed" - SessionEventTypeUserInputRequested SessionEventType = "user_input.requested" - SessionEventTypeUserMessage SessionEventType = "user.message" + SessionExtensionsLoadedDataExtensionsItemStatusRunning SessionExtensionsLoadedDataExtensionsItemStatus = "running" + SessionExtensionsLoadedDataExtensionsItemStatusDisabled SessionExtensionsLoadedDataExtensionsItemStatus = "disabled" + SessionExtensionsLoadedDataExtensionsItemStatusFailed SessionExtensionsLoadedDataExtensionsItemStatus = "failed" + SessionExtensionsLoadedDataExtensionsItemStatusStarting SessionExtensionsLoadedDataExtensionsItemStatus = "starting" ) -type ContextUnion struct { - ContextClass *ContextClass - String *string -} - -func (x *ContextUnion) UnmarshalJSON(data []byte) error { - x.ContextClass = nil - var c ContextClass - object, err := unmarshalUnion(data, nil, nil, nil, &x.String, false, nil, true, &c, false, nil, false, nil, false) - if err != nil { - return err - } - if object { - x.ContextClass = &c - } - return nil -} - -func (x *ContextUnion) MarshalJSON() ([]byte, error) { - return marshalUnion(nil, nil, nil, x.String, false, nil, x.ContextClass != nil, x.ContextClass, false, nil, false, nil, false) -} - -type ErrorUnion struct { - ErrorClass *ErrorClass - String *string -} - -func (x *ErrorUnion) UnmarshalJSON(data []byte) error { - x.ErrorClass = nil - var c ErrorClass - object, err := unmarshalUnion(data, nil, nil, nil, &x.String, false, nil, true, &c, false, nil, false, nil, false) - if err != nil { - return err - } - if object { - x.ErrorClass = &c - } - return nil -} - -func (x *ErrorUnion) MarshalJSON() ([]byte, error) { - return marshalUnion(nil, nil, nil, x.String, false, nil, x.ErrorClass != nil, x.ErrorClass, false, nil, false, nil, false) -} - -// The JSON-RPC request ID from the MCP protocol -type MCPRequestID struct { - Double *float64 - String *string -} - -func (x *MCPRequestID) UnmarshalJSON(data []byte) error { - object, err := unmarshalUnion(data, nil, &x.Double, nil, &x.String, false, nil, false, nil, false, nil, false, nil, false) - if err != nil { - return err - } - if object { - } - return nil -} - -func (x *MCPRequestID) MarshalJSON() ([]byte, error) { - return marshalUnion(nil, x.Double, nil, x.String, false, nil, false, nil, false, nil, false, nil, false) -} - -type RepositoryUnion struct { - RepositoryClass *RepositoryClass - String *string -} - -func (x *RepositoryUnion) UnmarshalJSON(data []byte) error { - x.RepositoryClass = nil - var c RepositoryClass - object, err := unmarshalUnion(data, nil, nil, nil, &x.String, false, nil, true, &c, false, nil, false, nil, false) - if err != nil { - return err - } - if object { - x.RepositoryClass = &c - } - return nil -} - -func (x *RepositoryUnion) MarshalJSON() ([]byte, error) { - return marshalUnion(nil, nil, nil, x.String, false, nil, x.RepositoryClass != nil, x.RepositoryClass, false, nil, false, nil, false) -} - -func unmarshalUnion(data []byte, pi **int64, pf **float64, pb **bool, ps **string, haveArray bool, pa interface{}, haveObject bool, pc interface{}, haveMap bool, pm interface{}, haveEnum bool, pe interface{}, nullable bool) (bool, error) { - if pi != nil { - *pi = nil - } - if pf != nil { - *pf = nil - } - if pb != nil { - *pb = nil - } - if ps != nil { - *ps = nil - } - - dec := json.NewDecoder(bytes.NewReader(data)) - dec.UseNumber() - tok, err := dec.Token() - if err != nil { - return false, err - } - - switch v := tok.(type) { - case json.Number: - if pi != nil { - i, err := v.Int64() - if err == nil { - *pi = &i - return false, nil - } - } - if pf != nil { - f, err := v.Float64() - if err == nil { - *pf = &f - return false, nil - } - return false, errors.New("Unparsable number") - } - return false, errors.New("Union does not contain number") - case float64: - return false, errors.New("Decoder should not return float64") - case bool: - if pb != nil { - *pb = &v - return false, nil - } - return false, errors.New("Union does not contain bool") - case string: - if haveEnum { - return false, json.Unmarshal(data, pe) - } - if ps != nil { - *ps = &v - return false, nil - } - return false, errors.New("Union does not contain string") - case nil: - if nullable { - return false, nil - } - return false, errors.New("Union does not contain null") - case json.Delim: - if v == '{' { - if haveObject { - return true, json.Unmarshal(data, pc) - } - if haveMap { - return false, json.Unmarshal(data, pm) - } - return false, errors.New("Union does not contain object") - } - if v == '[' { - if haveArray { - return false, json.Unmarshal(data, pa) - } - return false, errors.New("Union does not contain array") - } - return false, errors.New("Cannot handle delimiter") - } - return false, errors.New("Cannot unmarshal union") -} +// Type aliases for convenience. +type ( + PermissionRequest = PermissionRequestedDataPermissionRequest + PermissionRequestKind = PermissionRequestedDataPermissionRequestKind + PermissionRequestCommand = PermissionRequestedDataPermissionRequestCommandsItem + PossibleURL = PermissionRequestedDataPermissionRequestPossibleUrlsItem + Attachment = UserMessageDataAttachmentsItem + AttachmentType = UserMessageDataAttachmentsItemType +) -func marshalUnion(pi *int64, pf *float64, pb *bool, ps *string, haveArray bool, pa interface{}, haveObject bool, pc interface{}, haveMap bool, pm interface{}, haveEnum bool, pe interface{}, nullable bool) ([]byte, error) { - if pi != nil { - return json.Marshal(*pi) - } - if pf != nil { - return json.Marshal(*pf) - } - if pb != nil { - return json.Marshal(*pb) - } - if ps != nil { - return json.Marshal(*ps) - } - if haveArray { - return json.Marshal(pa) - } - if haveObject { - return json.Marshal(pc) - } - if haveMap { - return json.Marshal(pm) - } - if haveEnum { - return json.Marshal(pe) - } - if nullable { - return json.Marshal(nil) - } - return nil, errors.New("Union must not be null") -} +// Constant aliases for convenience. +const ( + AttachmentTypeFile = UserMessageDataAttachmentsItemTypeFile + AttachmentTypeDirectory = UserMessageDataAttachmentsItemTypeDirectory + AttachmentTypeSelection = UserMessageDataAttachmentsItemTypeSelection + AttachmentTypeGithubReference = UserMessageDataAttachmentsItemTypeGithubReference + AttachmentTypeBlob = UserMessageDataAttachmentsItemTypeBlob + PermissionRequestKindShell = PermissionRequestedDataPermissionRequestKindShell + PermissionRequestKindWrite = PermissionRequestedDataPermissionRequestKindWrite + PermissionRequestKindRead = PermissionRequestedDataPermissionRequestKindRead + PermissionRequestKindMcp = PermissionRequestedDataPermissionRequestKindMcp + PermissionRequestKindURL = PermissionRequestedDataPermissionRequestKindURL + PermissionRequestKindMemory = PermissionRequestedDataPermissionRequestKindMemory + PermissionRequestKindCustomTool = PermissionRequestedDataPermissionRequestKindCustomTool + PermissionRequestKindHook = PermissionRequestedDataPermissionRequestKindHook +) diff --git a/go/internal/e2e/commands_and_elicitation_test.go b/go/internal/e2e/commands_and_elicitation_test.go index 1d23bf1bd..fd88c1ade 100644 --- a/go/internal/e2e/commands_and_elicitation_test.go +++ b/go/internal/e2e/commands_and_elicitation_test.go @@ -77,11 +77,12 @@ func TestCommands(t *testing.T) { select { case event := <-commandsChangedCh: - if len(event.Data.Commands) == 0 { + d, ok := event.Data.(*copilot.CommandsChangedData) + if !ok || len(d.Commands) == 0 { t.Errorf("Expected commands in commands.changed event") } else { found := false - for _, cmd := range event.Data.Commands { + for _, cmd := range d.Commands { if cmd.Name == "deploy" { found = true if cmd.Description == nil || *cmd.Description != "Deploy the app" { @@ -91,7 +92,7 @@ func TestCommands(t *testing.T) { } } if !found { - t.Errorf("Expected 'deploy' command in commands.changed event, got %+v", event.Data.Commands) + t.Errorf("Expected 'deploy' command in commands.changed event, got %+v", d.Commands) } } case <-time.After(30 * time.Second): @@ -234,7 +235,7 @@ func TestUIElicitationMultiClient(t *testing.T) { capEnabledCh := make(chan copilot.SessionEvent, 1) unsubscribe := session1.On(func(event copilot.SessionEvent) { if event.Type == copilot.SessionEventTypeCapabilitiesChanged { - if event.Data.UI != nil && event.Data.UI.Elicitation != nil && *event.Data.UI.Elicitation { + if d, ok := event.Data.(*copilot.CapabilitiesChangedData); ok && d.UI != nil && d.UI.Elicitation != nil && *d.UI.Elicitation { select { case capEnabledCh <- event: default: @@ -262,8 +263,9 @@ func TestUIElicitationMultiClient(t *testing.T) { // Wait for the elicitation-enabled capabilities.changed event select { case capEvent := <-capEnabledCh: - if capEvent.Data.UI == nil || capEvent.Data.UI.Elicitation == nil || !*capEvent.Data.UI.Elicitation { - t.Errorf("Expected capabilities.changed with ui.elicitation=true, got %+v", capEvent.Data.UI) + capData, capOk := capEvent.Data.(*copilot.CapabilitiesChangedData) + if !capOk || capData.UI == nil || capData.UI.Elicitation == nil || !*capData.UI.Elicitation { + t.Errorf("Expected capabilities.changed with ui.elicitation=true, got %+v", capEvent.Data) } case <-time.After(30 * time.Second): t.Fatal("Timed out waiting for capabilities.changed event (elicitation enabled)") @@ -295,7 +297,7 @@ func TestUIElicitationMultiClient(t *testing.T) { capEnabledCh := make(chan struct{}, 1) unsubEnabled := session1.On(func(event copilot.SessionEvent) { if event.Type == copilot.SessionEventTypeCapabilitiesChanged { - if event.Data.UI != nil && event.Data.UI.Elicitation != nil && *event.Data.UI.Elicitation { + if d, ok := event.Data.(*copilot.CapabilitiesChangedData); ok && d.UI != nil && d.UI.Elicitation != nil && *d.UI.Elicitation { select { case capEnabledCh <- struct{}{}: default: @@ -334,7 +336,7 @@ func TestUIElicitationMultiClient(t *testing.T) { capDisabledCh := make(chan struct{}, 1) unsubDisabled := session1.On(func(event copilot.SessionEvent) { if event.Type == copilot.SessionEventTypeCapabilitiesChanged { - if event.Data.UI != nil && event.Data.UI.Elicitation != nil && !*event.Data.UI.Elicitation { + if d, ok := event.Data.(*copilot.CapabilitiesChangedData); ok && d.UI != nil && d.UI.Elicitation != nil && !*d.UI.Elicitation { select { case capDisabledCh <- struct{}{}: default: diff --git a/go/internal/e2e/compaction_test.go b/go/internal/e2e/compaction_test.go index 888ab2aa9..c980e558d 100644 --- a/go/internal/e2e/compaction_test.go +++ b/go/internal/e2e/compaction_test.go @@ -71,11 +71,12 @@ func TestCompaction(t *testing.T) { // Compaction should have succeeded if len(compactionCompleteEvents) > 0 { lastComplete := compactionCompleteEvents[len(compactionCompleteEvents)-1] - if lastComplete.Data.Success == nil || !*lastComplete.Data.Success { + d, ok := lastComplete.Data.(*copilot.SessionCompactionCompleteData) + if !ok || !d.Success { t.Errorf("Expected compaction to succeed") } - if lastComplete.Data.TokensRemoved != nil && *lastComplete.Data.TokensRemoved <= 0 { - t.Errorf("Expected tokensRemoved > 0, got %v", *lastComplete.Data.TokensRemoved) + if ok && d.TokensRemoved != nil && *d.TokensRemoved <= 0 { + t.Errorf("Expected tokensRemoved > 0, got %v", *d.TokensRemoved) } } @@ -84,8 +85,8 @@ func TestCompaction(t *testing.T) { if err != nil { t.Fatalf("Failed to send verification message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(strings.ToLower(*answer.Data.Content), "dragon") { - t.Errorf("Expected answer to contain 'dragon', got %v", answer.Data.Content) + if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(strings.ToLower(ad.Content), "dragon") { + t.Errorf("Expected answer to contain 'dragon', got %v", answer.Data) } }) diff --git a/go/internal/e2e/mcp_and_agents_test.go b/go/internal/e2e/mcp_and_agents_test.go index 079d26e9f..7b7d4d037 100644 --- a/go/internal/e2e/mcp_and_agents_test.go +++ b/go/internal/e2e/mcp_and_agents_test.go @@ -51,8 +51,8 @@ func TestMCPServers(t *testing.T) { t.Fatalf("Failed to get final message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "4") { - t.Errorf("Expected message to contain '4', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "4") { + t.Errorf("Expected message to contain '4', got: %v", message.Data) } session.Disconnect() @@ -100,8 +100,8 @@ func TestMCPServers(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "6") { - t.Errorf("Expected message to contain '6', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "6") { + t.Errorf("Expected message to contain '6', got: %v", message.Data) } session2.Disconnect() @@ -146,8 +146,8 @@ func TestMCPServers(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "hunter2") { - t.Errorf("Expected message to contain 'hunter2', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "hunter2") { + t.Errorf("Expected message to contain 'hunter2', got: %v", message.Data) } session.Disconnect() @@ -231,8 +231,8 @@ func TestCustomAgents(t *testing.T) { t.Fatalf("Failed to get final message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "10") { - t.Errorf("Expected message to contain '10', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "10") { + t.Errorf("Expected message to contain '10', got: %v", message.Data) } session.Disconnect() @@ -280,8 +280,8 @@ func TestCustomAgents(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "12") { - t.Errorf("Expected message to contain '12', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "12") { + t.Errorf("Expected message to contain '12', got: %v", message.Data) } session2.Disconnect() @@ -441,8 +441,8 @@ func TestCombinedConfiguration(t *testing.T) { t.Fatalf("Failed to get final message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "14") { - t.Errorf("Expected message to contain '14', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "14") { + t.Errorf("Expected message to contain '14', got: %v", message.Data) } session.Disconnect() diff --git a/go/internal/e2e/multi_client_test.go b/go/internal/e2e/multi_client_test.go index 406f118ce..389912284 100644 --- a/go/internal/e2e/multi_client_test.go +++ b/go/internal/e2e/multi_client_test.go @@ -112,7 +112,9 @@ func TestMultiClient(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if response == nil || response.Data.Content == nil || !strings.Contains(*response.Data.Content, "MAGIC_hello_42") { + if response == nil { + t.Errorf("Expected response to contain 'MAGIC_hello_42', got nil") + } else if rd, ok := response.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(rd.Content, "MAGIC_hello_42") { t.Errorf("Expected response to contain 'MAGIC_hello_42', got %v", response) } @@ -180,7 +182,9 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if response == nil || response.Data.Content == nil || *response.Data.Content == "" { + if response == nil { + t.Errorf("Expected non-empty response") + } else if rd, ok := response.Data.(*copilot.AssistantMessageData); !ok || rd.Content == "" { t.Errorf("Expected non-empty response") } @@ -222,8 +226,9 @@ func TestMultiClient(t *testing.T) { t.Errorf("Expected client 2 to see permission.completed events") } for _, event := range append(c1PermCompleted, c2PermCompleted...) { - if event.Data.Result == nil || event.Data.Result.Kind == nil || *event.Data.Result.Kind != "approved" { - t.Errorf("Expected permission.completed result kind 'approved', got %v", event.Data.Result) + d, ok := event.Data.(*copilot.PermissionCompletedData) + if !ok || string(d.Result.Kind) != "approved" { + t.Errorf("Expected permission.completed result kind 'approved', got %v", event.Data) } } @@ -318,8 +323,9 @@ func TestMultiClient(t *testing.T) { t.Errorf("Expected client 2 to see permission.completed events") } for _, event := range append(c1PermCompleted, c2PermCompleted...) { - if event.Data.Result == nil || event.Data.Result.Kind == nil || *event.Data.Result.Kind != "denied-interactively-by-user" { - t.Errorf("Expected permission.completed result kind 'denied-interactively-by-user', got %v", event.Data.Result) + d, ok := event.Data.(*copilot.PermissionCompletedData) + if !ok || string(d.Result.Kind) != "denied-interactively-by-user" { + t.Errorf("Expected permission.completed result kind 'denied-interactively-by-user', got %v", event.Data) } } @@ -368,11 +374,15 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if response1 == nil || response1.Data.Content == nil { + if response1 == nil { t.Fatalf("Expected response with content") } - if !strings.Contains(*response1.Data.Content, "CITY_FOR_US") { - t.Errorf("Expected response to contain 'CITY_FOR_US', got '%s'", *response1.Data.Content) + rd1, ok := response1.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + if !strings.Contains(rd1.Content, "CITY_FOR_US") { + t.Errorf("Expected response to contain 'CITY_FOR_US', got '%s'", rd1.Content) } response2, err := session1.SendAndWait(t.Context(), copilot.MessageOptions{ @@ -381,11 +391,15 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if response2 == nil || response2.Data.Content == nil { + if response2 == nil { t.Fatalf("Expected response with content") } - if !strings.Contains(*response2.Data.Content, "CURRENCY_FOR_US") { - t.Errorf("Expected response to contain 'CURRENCY_FOR_US', got '%s'", *response2.Data.Content) + rd2, ok := response2.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + if !strings.Contains(rd2.Content, "CURRENCY_FOR_US") { + t.Errorf("Expected response to contain 'CURRENCY_FOR_US', got '%s'", rd2.Content) } session2.Disconnect() @@ -433,11 +447,15 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if stableResponse == nil || stableResponse.Data.Content == nil { + if stableResponse == nil { t.Fatalf("Expected response with content") } - if !strings.Contains(*stableResponse.Data.Content, "STABLE_test1") { - t.Errorf("Expected response to contain 'STABLE_test1', got '%s'", *stableResponse.Data.Content) + srd, ok := stableResponse.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + if !strings.Contains(srd.Content, "STABLE_test1") { + t.Errorf("Expected response to contain 'STABLE_test1', got '%s'", srd.Content) } ephemeralResponse, err := session1.SendAndWait(t.Context(), copilot.MessageOptions{ @@ -446,11 +464,15 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if ephemeralResponse == nil || ephemeralResponse.Data.Content == nil { + if ephemeralResponse == nil { t.Fatalf("Expected response with content") } - if !strings.Contains(*ephemeralResponse.Data.Content, "EPHEMERAL_test2") { - t.Errorf("Expected response to contain 'EPHEMERAL_test2', got '%s'", *ephemeralResponse.Data.Content) + erd, ok := ephemeralResponse.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + if !strings.Contains(erd.Content, "EPHEMERAL_test2") { + t.Errorf("Expected response to contain 'EPHEMERAL_test2', got '%s'", erd.Content) } // Disconnect client 2 without destroying the shared session @@ -471,15 +493,19 @@ func TestMultiClient(t *testing.T) { if err != nil { t.Fatalf("Failed to send message: %v", err) } - if afterResponse == nil || afterResponse.Data.Content == nil { + if afterResponse == nil { t.Fatalf("Expected response with content") } - if !strings.Contains(*afterResponse.Data.Content, "STABLE_still_here") { - t.Errorf("Expected response to contain 'STABLE_still_here', got '%s'", *afterResponse.Data.Content) + ard, ok := afterResponse.Data.(*copilot.AssistantMessageData) + if !ok { + t.Fatalf("Expected AssistantMessageData") + } + if !strings.Contains(ard.Content, "STABLE_still_here") { + t.Errorf("Expected response to contain 'STABLE_still_here', got '%s'", ard.Content) } // ephemeral_tool should NOT have produced a result - if strings.Contains(*afterResponse.Data.Content, "EPHEMERAL_") { - t.Errorf("Expected response NOT to contain 'EPHEMERAL_', got '%s'", *afterResponse.Data.Content) + if strings.Contains(ard.Content, "EPHEMERAL_") { + t.Errorf("Expected response NOT to contain 'EPHEMERAL_', got '%s'", ard.Content) } }) } diff --git a/go/internal/e2e/permissions_test.go b/go/internal/e2e/permissions_test.go index 98f620043..784cf897f 100644 --- a/go/internal/e2e/permissions_test.go +++ b/go/internal/e2e/permissions_test.go @@ -173,13 +173,15 @@ func TestPermissions(t *testing.T) { permissionDenied := false session.On(func(event copilot.SessionEvent) { - if event.Type == copilot.SessionEventTypeToolExecutionComplete && - event.Data.Success != nil && !*event.Data.Success && - event.Data.Error != nil && event.Data.Error.ErrorClass != nil && - strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") { - mu.Lock() - permissionDenied = true - mu.Unlock() + if event.Type == copilot.SessionEventTypeToolExecutionComplete { + if d, ok := event.Data.(*copilot.ToolExecutionCompleteData); ok && + !d.Success && + d.Error != nil && + strings.Contains(d.Error.Message, "Permission denied") { + mu.Lock() + permissionDenied = true + mu.Unlock() + } } }) @@ -223,13 +225,15 @@ func TestPermissions(t *testing.T) { permissionDenied := false session2.On(func(event copilot.SessionEvent) { - if event.Type == copilot.SessionEventTypeToolExecutionComplete && - event.Data.Success != nil && !*event.Data.Success && - event.Data.Error != nil && event.Data.Error.ErrorClass != nil && - strings.Contains(event.Data.Error.ErrorClass.Message, "Permission denied") { - mu.Lock() - permissionDenied = true - mu.Unlock() + if event.Type == copilot.SessionEventTypeToolExecutionComplete { + if d, ok := event.Data.(*copilot.ToolExecutionCompleteData); ok && + !d.Success && + d.Error != nil && + strings.Contains(d.Error.Message, "Permission denied") { + mu.Lock() + permissionDenied = true + mu.Unlock() + } } }) @@ -266,8 +270,12 @@ func TestPermissions(t *testing.T) { t.Fatalf("Failed to get final message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, "4") { - t.Errorf("Expected message to contain '4', got: %v", message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "4") { + var content string + if ok { + content = md.Content + } + t.Errorf("Expected message to contain '4', got: %v", content) } }) } diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go index 0f51791db..d08607ba4 100644 --- a/go/internal/e2e/session_fs_test.go +++ b/go/internal/e2e/session_fs_test.go @@ -48,8 +48,10 @@ func TestSessionFs(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } content := "" - if msg != nil && msg.Data.Content != nil { - content = *msg.Data.Content + if msg != nil { + if d, ok := msg.Data.(*copilot.AssistantMessageData); ok { + content = d.Content + } } if !strings.Contains(content, "300") { t.Fatalf("Expected response to contain 300, got %q", content) @@ -84,8 +86,10 @@ func TestSessionFs(t *testing.T) { t.Fatalf("Failed to send first message: %v", err) } content := "" - if msg != nil && msg.Data.Content != nil { - content = *msg.Data.Content + if msg != nil { + if d, ok := msg.Data.(*copilot.AssistantMessageData); ok { + content = d.Content + } } if !strings.Contains(content, "100") { t.Fatalf("Expected response to contain 100, got %q", content) @@ -111,8 +115,10 @@ func TestSessionFs(t *testing.T) { t.Fatalf("Failed to send second message: %v", err) } content2 := "" - if msg2 != nil && msg2.Data.Content != nil { - content2 = *msg2.Data.Content + if msg2 != nil { + if d, ok := msg2.Data.(*copilot.AssistantMessageData); ok { + content2 = d.Content + } } if !strings.Contains(content2, "300") { t.Fatalf("Expected response to contain 300, got %q", content2) @@ -396,12 +402,10 @@ func providerPath(root string, sessionID string, path string) string { func findToolCallResult(messages []copilot.SessionEvent, toolName string) string { for _, message := range messages { - if message.Type == "tool.execution_complete" && - message.Data.Result != nil && - message.Data.Result.Content != nil && - message.Data.ToolCallID != nil && - findToolName(messages, *message.Data.ToolCallID) == toolName { - return *message.Data.Result.Content + if d, ok := message.Data.(*copilot.ToolExecutionCompleteData); ok && + d.Result != nil && + findToolName(messages, d.ToolCallID) == toolName { + return d.Result.Content } } return "" @@ -409,11 +413,9 @@ func findToolCallResult(messages []copilot.SessionEvent, toolName string) string func findToolName(messages []copilot.SessionEvent, toolCallID string) string { for _, message := range messages { - if message.Type == "tool.execution_start" && - message.Data.ToolCallID != nil && - *message.Data.ToolCallID == toolCallID && - message.Data.ToolName != nil { - return *message.Data.ToolName + if d, ok := message.Data.(*copilot.ToolExecutionStartData); ok && + d.ToolCallID == toolCallID { + return d.ToolName } } return "" diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index 35824819a..813036545 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -42,12 +42,13 @@ func TestSession(t *testing.T) { t.Fatalf("Expected first message to be session.start, got %v", messages) } - if messages[0].Data.SessionID == nil || *messages[0].Data.SessionID != session.SessionID { + startData, startOk := messages[0].Data.(*copilot.SessionStartData) + if !startOk || startData.SessionID != session.SessionID { t.Errorf("Expected session.start sessionId to match") } - if messages[0].Data.SelectedModel == nil || *messages[0].Data.SelectedModel != "claude-sonnet-4.5" { - t.Errorf("Expected selectedModel to be 'claude-sonnet-4.5', got %v", messages[0].Data.SelectedModel) + if !startOk || startData.SelectedModel == nil || *startData.SelectedModel != "claude-sonnet-4.5" { + t.Errorf("Expected selectedModel to be 'claude-sonnet-4.5', got %v", startData) } if err := session.Disconnect(); err != nil { @@ -73,8 +74,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if assistantMessage.Data.Content == nil || !strings.Contains(*assistantMessage.Data.Content, "2") { - t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data.Content) + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "2") { + t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data) } secondMessage, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Now if you double that, what do you get?"}) @@ -82,8 +83,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to send second message: %v", err) } - if secondMessage.Data.Content == nil || !strings.Contains(*secondMessage.Data.Content, "4") { - t.Errorf("Expected second message to contain '4', got %v", secondMessage.Data.Content) + if ad, ok := secondMessage.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "4") { + t.Errorf("Expected second message to contain '4', got %v", secondMessage.Data) } }) @@ -108,8 +109,10 @@ func TestSession(t *testing.T) { } content := "" - if assistantMessage != nil && assistantMessage.Data.Content != nil { - content = *assistantMessage.Data.Content + if assistantMessage != nil { + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content + } } if !strings.Contains(content, "GitHub") { @@ -162,8 +165,8 @@ func TestSession(t *testing.T) { } content := "" - if assistantMessage.Data.Content != nil { - content = *assistantMessage.Data.Content + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content } if strings.Contains(content, "GitHub") { @@ -361,8 +364,8 @@ func TestSession(t *testing.T) { } content := "" - if assistantMessage.Data.Content != nil { - content = *assistantMessage.Data.Content + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content } if !strings.Contains(content, "54321") { @@ -394,8 +397,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "2") { - t.Errorf("Expected answer to contain '2', got %v", answer.Data.Content) + if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "2") { + t.Errorf("Expected answer to contain '2', got %v", answer.Data) } // Resume using the same client @@ -415,8 +418,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to get assistant message from resumed session: %v", err) } - if answer2.Data.Content == nil || !strings.Contains(*answer2.Data.Content, "2") { - t.Errorf("Expected resumed session answer to contain '2', got %v", answer2.Data.Content) + if ad, ok := answer2.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "2") { + t.Errorf("Expected resumed session answer to contain '2', got %v", answer2.Data) } // Can continue the conversation statefully @@ -424,7 +427,9 @@ func TestSession(t *testing.T) { if err != nil { t.Fatalf("Failed to send follow-up message: %v", err) } - if answer3 == nil || answer3.Data.Content == nil || !strings.Contains(*answer3.Data.Content, "4") { + if answer3 == nil { + t.Errorf("Expected follow-up answer to contain '4', got nil") + } else if ad, ok := answer3.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "4") { t.Errorf("Expected follow-up answer to contain '4', got %v", answer3) } }) @@ -449,8 +454,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "2") { - t.Errorf("Expected answer to contain '2', got %v", answer.Data.Content) + if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "2") { + t.Errorf("Expected answer to contain '2', got %v", answer.Data) } // Resume using a new client @@ -497,7 +502,9 @@ func TestSession(t *testing.T) { if err != nil { t.Fatalf("Failed to send follow-up message: %v", err) } - if answer3 == nil || answer3.Data.Content == nil || !strings.Contains(*answer3.Data.Content, "4") { + if answer3 == nil { + t.Errorf("Expected follow-up answer to contain '4', got nil") + } else if ad, ok := answer3.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "4") { t.Errorf("Expected follow-up answer to contain '4', got %v", answer3) } }) @@ -628,8 +635,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to send message after abort: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "4") { - t.Errorf("Expected answer to contain '4', got %v", answer.Data.Content) + if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "4") { + t.Errorf("Expected answer to contain '4', got %v", answer.Data) } }) @@ -723,8 +730,8 @@ func TestSession(t *testing.T) { if err != nil { t.Fatalf("Failed to get assistant message: %v", err) } - if assistantMessage.Data.Content == nil || !strings.Contains(*assistantMessage.Data.Content, "300") { - t.Errorf("Expected assistant message to contain '300', got %v", assistantMessage.Data.Content) + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "300") { + t.Errorf("Expected assistant message to contain '300', got %v", assistantMessage.Data) } }) @@ -756,8 +763,8 @@ func TestSession(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if assistantMessage.Data.Content == nil || !strings.Contains(*assistantMessage.Data.Content, "2") { - t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data.Content) + if ad, ok := assistantMessage.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "2") { + t.Errorf("Expected assistant message to contain '2', got %v", assistantMessage.Data) } }) @@ -1032,11 +1039,12 @@ func TestSetModelWithReasoningEffort(t *testing.T) { select { case evt := <-modelChanged: - if evt.Data.NewModel == nil || *evt.Data.NewModel != "gpt-4.1" { - t.Errorf("Expected newModel 'gpt-4.1', got %v", evt.Data.NewModel) + md, mdOk := evt.Data.(*copilot.SessionModelChangeData) + if !mdOk || md.NewModel != "gpt-4.1" { + t.Errorf("Expected newModel 'gpt-4.1', got %v", evt.Data) } - if evt.Data.ReasoningEffort == nil || *evt.Data.ReasoningEffort != "high" { - t.Errorf("Expected reasoningEffort 'high', got %v", evt.Data.ReasoningEffort) + if !mdOk || md.ReasoningEffort == nil || *md.ReasoningEffort != "high" { + t.Errorf("Expected reasoningEffort 'high', got %v", evt.Data) } case <-time.After(30 * time.Second): t.Fatal("Timed out waiting for session.model_change event") @@ -1139,11 +1147,12 @@ func TestSessionLog(t *testing.T) { } evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Info message", 5*time.Second) - if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" { - t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType) + id, idOk := evt.Data.(*copilot.SessionInfoData) + if !idOk || id.InfoType != "notification" { + t.Errorf("Expected infoType 'notification', got %v", evt.Data) } - if evt.Data.Message == nil || *evt.Data.Message != "Info message" { - t.Errorf("Expected message 'Info message', got %v", evt.Data.Message) + if !idOk || id.Message != "Info message" { + t.Errorf("Expected message 'Info message', got %v", evt.Data) } }) @@ -1153,11 +1162,12 @@ func TestSessionLog(t *testing.T) { } evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionWarning, "Warning message", 5*time.Second) - if evt.Data.WarningType == nil || *evt.Data.WarningType != "notification" { - t.Errorf("Expected warningType 'notification', got %v", evt.Data.WarningType) + wd, wdOk := evt.Data.(*copilot.SessionWarningData) + if !wdOk || wd.WarningType != "notification" { + t.Errorf("Expected warningType 'notification', got %v", evt.Data) } - if evt.Data.Message == nil || *evt.Data.Message != "Warning message" { - t.Errorf("Expected message 'Warning message', got %v", evt.Data.Message) + if !wdOk || wd.Message != "Warning message" { + t.Errorf("Expected message 'Warning message', got %v", evt.Data) } }) @@ -1167,11 +1177,12 @@ func TestSessionLog(t *testing.T) { } evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionError, "Error message", 5*time.Second) - if evt.Data.ErrorType == nil || *evt.Data.ErrorType != "notification" { - t.Errorf("Expected errorType 'notification', got %v", evt.Data.ErrorType) + ed, edOk := evt.Data.(*copilot.SessionErrorData) + if !edOk || ed.ErrorType != "notification" { + t.Errorf("Expected errorType 'notification', got %v", evt.Data) } - if evt.Data.Message == nil || *evt.Data.Message != "Error message" { - t.Errorf("Expected message 'Error message', got %v", evt.Data.Message) + if !edOk || ed.Message != "Error message" { + t.Errorf("Expected message 'Error message', got %v", evt.Data) } }) @@ -1181,11 +1192,12 @@ func TestSessionLog(t *testing.T) { } evt := waitForEvent(t, &mu, &events, copilot.SessionEventTypeSessionInfo, "Ephemeral message", 5*time.Second) - if evt.Data.InfoType == nil || *evt.Data.InfoType != "notification" { - t.Errorf("Expected infoType 'notification', got %v", evt.Data.InfoType) + id2, id2Ok := evt.Data.(*copilot.SessionInfoData) + if !id2Ok || id2.InfoType != "notification" { + t.Errorf("Expected infoType 'notification', got %v", evt.Data) } - if evt.Data.Message == nil || *evt.Data.Message != "Ephemeral message" { - t.Errorf("Expected message 'Ephemeral message', got %v", evt.Data.Message) + if !id2Ok || id2.Message != "Ephemeral message" { + t.Errorf("Expected message 'Ephemeral message', got %v", evt.Data) } }) } @@ -1197,7 +1209,7 @@ func waitForEvent(t *testing.T, mu *sync.Mutex, events *[]copilot.SessionEvent, for time.Now().Before(deadline) { mu.Lock() for _, evt := range *events { - if evt.Type == eventType && evt.Data.Message != nil && *evt.Data.Message == message { + if evt.Type == eventType && getEventMessage(evt) == message { mu.Unlock() return evt } @@ -1208,3 +1220,17 @@ func waitForEvent(t *testing.T, mu *sync.Mutex, events *[]copilot.SessionEvent, t.Fatalf("Timed out waiting for %s event with message %q", eventType, message) return copilot.SessionEvent{} // unreachable } + +// getEventMessage extracts the Message field from session info/warning/error event data. +func getEventMessage(evt copilot.SessionEvent) string { + switch d := evt.Data.(type) { + case *copilot.SessionInfoData: + return d.Message + case *copilot.SessionWarningData: + return d.Message + case *copilot.SessionErrorData: + return d.Message + default: + return "" + } +} diff --git a/go/internal/e2e/skills_test.go b/go/internal/e2e/skills_test.go index 524280fd8..f6943fef9 100644 --- a/go/internal/e2e/skills_test.go +++ b/go/internal/e2e/skills_test.go @@ -72,8 +72,8 @@ func TestSkills(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message.Data.Content == nil || !strings.Contains(*message.Data.Content, skillMarker) { - t.Errorf("Expected message to contain skill marker '%s', got: %v", skillMarker, message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to contain skill marker '%s', got: %v", skillMarker, message.Data) } session.Disconnect() @@ -101,8 +101,8 @@ func TestSkills(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message.Data.Content != nil && strings.Contains(*message.Data.Content, skillMarker) { - t.Errorf("Expected message to NOT contain skill marker '%s' when disabled, got: %v", skillMarker, *message.Data.Content) + if md, ok := message.Data.(*copilot.AssistantMessageData); ok && strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to NOT contain skill marker '%s' when disabled, got: %v", skillMarker, md.Content) } session.Disconnect() @@ -127,8 +127,8 @@ func TestSkills(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message1.Data.Content != nil && strings.Contains(*message1.Data.Content, skillMarker) { - t.Errorf("Expected message to NOT contain skill marker before skill was added, got: %v", *message1.Data.Content) + if md, ok := message1.Data.(*copilot.AssistantMessageData); ok && strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to NOT contain skill marker before skill was added, got: %v", md.Content) } // Resume with skillDirectories - skill should now be active @@ -150,8 +150,8 @@ func TestSkills(t *testing.T) { t.Fatalf("Failed to send message: %v", err) } - if message2.Data.Content == nil || !strings.Contains(*message2.Data.Content, skillMarker) { - t.Errorf("Expected message to contain skill marker '%s' after resume, got: %v", skillMarker, message2.Data.Content) + if md, ok := message2.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, skillMarker) { + t.Errorf("Expected message to contain skill marker '%s' after resume, got: %v", skillMarker, message2.Data) } session2.Disconnect() diff --git a/go/internal/e2e/streaming_fidelity_test.go b/go/internal/e2e/streaming_fidelity_test.go index ef76c3d8b..9b4fb13aa 100644 --- a/go/internal/e2e/streaming_fidelity_test.go +++ b/go/internal/e2e/streaming_fidelity_test.go @@ -47,7 +47,7 @@ func TestStreamingFidelity(t *testing.T) { // Deltas should have content for _, delta := range deltaEvents { - if delta.Data.DeltaContent == nil { + if dd, ok := delta.Data.(*copilot.AssistantMessageDeltaData); !ok || dd.DeltaContent == "" { t.Error("Expected delta to have content") } } @@ -161,7 +161,9 @@ func TestStreamingFidelity(t *testing.T) { if err != nil { t.Fatalf("Failed to send follow-up message: %v", err) } - if answer == nil || answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "18") { + if answer == nil { + t.Errorf("Expected answer to contain '18', got nil") + } else if ad, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(ad.Content, "18") { t.Errorf("Expected answer to contain '18', got %v", answer) } @@ -178,7 +180,7 @@ func TestStreamingFidelity(t *testing.T) { // Deltas should have content for _, delta := range deltaEvents { - if delta.Data.DeltaContent == nil { + if dd, ok := delta.Data.(*copilot.AssistantMessageDeltaData); !ok || dd.DeltaContent == "" { t.Error("Expected delta to have content") } } diff --git a/go/internal/e2e/testharness/helper.go b/go/internal/e2e/testharness/helper.go index d55f90c1b..0960b659d 100644 --- a/go/internal/e2e/testharness/helper.go +++ b/go/internal/e2e/testharness/helper.go @@ -18,19 +18,15 @@ func GetFinalAssistantMessage(ctx context.Context, session *copilot.Session, alr // Subscribe to future events var finalAssistantMessage *copilot.SessionEvent unsubscribe := session.On(func(event copilot.SessionEvent) { - switch event.Type { - case "assistant.message": + switch d := event.Data.(type) { + case *copilot.AssistantMessageData: finalAssistantMessage = &event - case "session.idle": + case *copilot.SessionIdleData: if finalAssistantMessage != nil { result <- finalAssistantMessage } - case "session.error": - msg := "session error" - if event.Data.Message != nil { - msg = *event.Data.Message - } - errCh <- errors.New(msg) + case *copilot.SessionErrorData: + errCh <- errors.New(d.Message) } }) defer unsubscribe() @@ -72,8 +68,8 @@ func GetNextEventOfType(session *copilot.Session, eventType copilot.SessionEvent } case copilot.SessionEventTypeSessionError: msg := "session error" - if event.Data.Message != nil { - msg = *event.Data.Message + if d, ok := event.Data.(*copilot.SessionErrorData); ok { + msg = d.Message } select { case errCh <- errors.New(msg): @@ -119,8 +115,8 @@ func getExistingFinalResponse(ctx context.Context, session *copilot.Session, alr for _, msg := range currentTurnMessages { if msg.Type == "session.error" { errMsg := "session error" - if msg.Data.Message != nil { - errMsg = *msg.Data.Message + if d, ok := msg.Data.(*copilot.SessionErrorData); ok { + errMsg = d.Message } return nil, errors.New(errMsg) } diff --git a/go/internal/e2e/tool_results_test.go b/go/internal/e2e/tool_results_test.go index b35d9b5d0..2d9ebd382 100644 --- a/go/internal/e2e/tool_results_test.go +++ b/go/internal/e2e/tool_results_test.go @@ -47,8 +47,8 @@ func TestToolResults(t *testing.T) { } content := "" - if answer.Data.Content != nil { - content = *answer.Data.Content + if ad, ok := answer.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content } if !strings.Contains(strings.ToLower(content), "sunny") && !strings.Contains(content, "72") { t.Errorf("Expected answer to mention sunny or 72, got %q", content) @@ -95,8 +95,8 @@ func TestToolResults(t *testing.T) { } content := "" - if answer.Data.Content != nil { - content = *answer.Data.Content + if ad, ok := answer.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content } if !strings.Contains(strings.ToLower(content), "service is down") { t.Errorf("Expected 'service is down', got %q", content) @@ -145,8 +145,8 @@ func TestToolResults(t *testing.T) { } content := "" - if answer.Data.Content != nil { - content = *answer.Data.Content + if ad, ok := answer.Data.(*copilot.AssistantMessageData); ok { + content = ad.Content } if !strings.Contains(strings.ToLower(content), "no issues") { t.Errorf("Expected 'no issues', got %q", content) diff --git a/go/internal/e2e/tools_test.go b/go/internal/e2e/tools_test.go index c9676363f..c67ae1b5d 100644 --- a/go/internal/e2e/tools_test.go +++ b/go/internal/e2e/tools_test.go @@ -43,8 +43,8 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "ELIZA") { - t.Errorf("Expected answer to contain 'ELIZA', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "ELIZA") { + t.Errorf("Expected answer to contain 'ELIZA', got %v", answer.Data) } }) @@ -78,8 +78,8 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "HELLO") { - t.Errorf("Expected answer to contain 'HELLO', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "HELLO") { + t.Errorf("Expected answer to contain 'HELLO', got %v", answer.Data) } }) @@ -162,11 +162,11 @@ func TestTools(t *testing.T) { } // The assistant should not see the exception information - if answer.Data.Content != nil && strings.Contains(*answer.Data.Content, "Melbourne") { - t.Errorf("Assistant should not see error details 'Melbourne', got '%s'", *answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); ok && strings.Contains(md.Content, "Melbourne") { + t.Errorf("Assistant should not see error details 'Melbourne', got '%s'", md.Content) } - if answer.Data.Content == nil || !strings.Contains(strings.ToLower(*answer.Data.Content), "unknown") { - t.Errorf("Expected answer to contain 'unknown', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(strings.ToLower(md.Content), "unknown") { + t.Errorf("Expected answer to contain 'unknown', got %v", answer.Data) } }) @@ -232,11 +232,15 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer == nil || answer.Data.Content == nil { + if answer == nil { + t.Fatalf("Expected assistant message with content") + } + ad, ok := answer.Data.(*copilot.AssistantMessageData) + if !ok { t.Fatalf("Expected assistant message with content") } - responseContent := *answer.Data.Content + responseContent := ad.Content if responseContent == "" { t.Errorf("Expected non-empty response") } @@ -301,8 +305,8 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "RESULT: test123") { - t.Errorf("Expected answer to contain 'RESULT: test123', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "RESULT: test123") { + t.Errorf("Expected answer to contain 'RESULT: test123', got %v", answer.Data) } if didRunPermissionRequest { @@ -343,8 +347,8 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "CUSTOM_GREP_RESULT") { - t.Errorf("Expected answer to contain 'CUSTOM_GREP_RESULT', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "CUSTOM_GREP_RESULT") { + t.Errorf("Expected answer to contain 'CUSTOM_GREP_RESULT', got %v", answer.Data) } }) @@ -386,8 +390,8 @@ func TestTools(t *testing.T) { t.Fatalf("Failed to get assistant message: %v", err) } - if answer.Data.Content == nil || !strings.Contains(*answer.Data.Content, "HELLO") { - t.Errorf("Expected answer to contain 'HELLO', got %v", answer.Data.Content) + if md, ok := answer.Data.(*copilot.AssistantMessageData); !ok || !strings.Contains(md.Content, "HELLO") { + t.Errorf("Expected answer to contain 'HELLO', got %v", answer.Data) } // Should have received a custom-tool permission request diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index c32510083..97d886e48 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -961,7 +961,7 @@ const ( FormatDate Format = "date" FormatDateTime Format = "date-time" FormatEmail Format = "email" - FormatUri Format = "uri" + FormatURI Format = "uri" ) type ItemsType string @@ -1654,9 +1654,9 @@ func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *SessionC return &result, nil } -type UiApi sessionApi +type UIApi sessionApi -func (a *UiApi) Elicitation(ctx context.Context, params *SessionUIElicitationParams) (*SessionUIElicitationResult, error) { +func (a *UIApi) Elicitation(ctx context.Context, params *SessionUIElicitationParams) (*SessionUIElicitationResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["message"] = params.Message @@ -1673,7 +1673,7 @@ func (a *UiApi) Elicitation(ctx context.Context, params *SessionUIElicitationPar return &result, nil } -func (a *UiApi) HandlePendingElicitation(ctx context.Context, params *SessionUIHandlePendingElicitationParams) (*SessionUIHandlePendingElicitationResult, error) { +func (a *UIApi) HandlePendingElicitation(ctx context.Context, params *SessionUIHandlePendingElicitationParams) (*SessionUIHandlePendingElicitationResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -1769,7 +1769,7 @@ type SessionRpc struct { Compaction *CompactionApi Tools *ToolsApi Commands *CommandsApi - Ui *UiApi + UI *UIApi Permissions *PermissionsApi Shell *ShellApi } @@ -1815,7 +1815,7 @@ func NewSessionRpc(client *jsonrpc2.Client, sessionID string) *SessionRpc { r.Compaction = (*CompactionApi)(&r.common) r.Tools = (*ToolsApi)(&r.common) r.Commands = (*CommandsApi)(&r.common) - r.Ui = (*UiApi)(&r.common) + r.UI = (*UIApi)(&r.common) r.Permissions = (*PermissionsApi)(&r.common) r.Shell = (*ShellApi)(&r.common) return r diff --git a/go/samples/chat.go b/go/samples/chat.go index 677aafdfe..62faaca72 100644 --- a/go/samples/chat.go +++ b/go/samples/chat.go @@ -34,15 +34,11 @@ func main() { session.On(func(event copilot.SessionEvent) { var output string - switch event.Type { - case copilot.SessionEventTypeAssistantReasoning: - if event.Data.Content != nil { - output = fmt.Sprintf("[reasoning: %s]", *event.Data.Content.String) - } - case copilot.SessionEventTypeToolExecutionStart: - if event.Data.ToolName != nil { - output = fmt.Sprintf("[tool: %s]", *event.Data.ToolName) - } + switch d := event.Data.(type) { + case *copilot.AssistantReasoningData: + output = fmt.Sprintf("[reasoning: %s]", d.Content) + case *copilot.ToolExecutionStartData: + output = fmt.Sprintf("[tool: %s]", d.ToolName) } if output != "" { fmt.Printf("%s%s%s\n", blue, output, reset) @@ -65,8 +61,10 @@ func main() { reply, _ := session.SendAndWait(ctx, copilot.MessageOptions{Prompt: input}) content := "" - if reply != nil && reply.Data.Content != nil { - content = *reply.Data.Content.String + if reply != nil { + if d, ok := reply.Data.(*copilot.AssistantMessageData); ok { + content = d.Content + } } fmt.Printf("\nAssistant: %s\n\n", content) } diff --git a/go/session.go b/go/session.go index 8108180cc..fde0d9875 100644 --- a/go/session.go +++ b/go/session.go @@ -38,8 +38,8 @@ type sessionHandler struct { // // // Subscribe to events // unsubscribe := session.On(func(event copilot.SessionEvent) { -// if event.Type == "assistant.message" { -// fmt.Println("Assistant:", event.Data.Content) +// if d, ok := event.Data.(*copilot.AssistantMessageData); ok { +// fmt.Println("Assistant:", d.Content) // } // }) // defer unsubscribe() @@ -177,7 +177,9 @@ func (s *Session) Send(ctx context.Context, options MessageOptions) (string, err // log.Printf("Failed: %v", err) // } // if response != nil { -// fmt.Println(*response.Data.Content) +// if d, ok := response.Data.(*AssistantMessageData); ok { +// fmt.Println(d.Content) +// } // } func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*SessionEvent, error) { if _, ok := ctx.Deadline(); !ok { @@ -192,24 +194,20 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses var mu sync.Mutex unsubscribe := s.On(func(event SessionEvent) { - switch event.Type { - case SessionEventTypeAssistantMessage: + switch d := event.Data.(type) { + case *AssistantMessageData: mu.Lock() eventCopy := event lastAssistantMessage = &eventCopy mu.Unlock() - case SessionEventTypeSessionIdle: + case *SessionIdleData: select { case idleCh <- struct{}{}: default: } - case SessionEventTypeSessionError: - errMsg := "session error" - if event.Data.Message != nil { - errMsg = *event.Data.Message - } + case *SessionErrorData: select { - case errCh <- fmt.Errorf("session error: %s", errMsg): + case errCh <- fmt.Errorf("session error: %s", d.Message): default: } } @@ -246,11 +244,11 @@ func (s *Session) SendAndWait(ctx context.Context, options MessageOptions) (*Ses // Example: // // unsubscribe := session.On(func(event copilot.SessionEvent) { -// switch event.Type { -// case "assistant.message": -// fmt.Println("Assistant:", event.Data.Content) -// case "session.error": -// fmt.Println("Error:", event.Data.Message) +// switch d := event.Data.(type) { +// case *copilot.AssistantMessageData: +// fmt.Println("Assistant:", d.Content) +// case *copilot.SessionErrorData: +// fmt.Println("Error:", d.Message) // } // }) // @@ -590,7 +588,7 @@ func (s *Session) handleElicitationRequest(elicitCtx ElicitationContext, request result, err := handler(elicitCtx) if err != nil { // Handler failed — attempt to cancel so the request doesn't hang. - s.RPC.Ui.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ + s.RPC.UI.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ RequestID: requestID, Result: rpc.SessionUIHandlePendingElicitationParamsResult{ Action: rpc.ActionCancel, @@ -604,7 +602,7 @@ func (s *Session) handleElicitationRequest(elicitCtx ElicitationContext, request rpcContent[k] = toRPCContent(v) } - s.RPC.Ui.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ + s.RPC.UI.HandlePendingElicitation(ctx, &rpc.SessionUIHandlePendingElicitationParams{ RequestID: requestID, Result: rpc.SessionUIHandlePendingElicitationParamsResult{ Action: rpc.Action(result.Action), @@ -685,7 +683,7 @@ func (ui *SessionUI) Elicitation(ctx context.Context, message string, requestedS if err := ui.session.assertElicitation(); err != nil { return nil, err } - rpcResult, err := ui.session.RPC.Ui.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ Message: message, RequestedSchema: requestedSchema, }) @@ -702,7 +700,7 @@ func (ui *SessionUI) Confirm(ctx context.Context, message string) (bool, error) return false, err } defaultTrue := &rpc.Content{Bool: Bool(true)} - rpcResult, err := ui.session.RPC.Ui.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ Message: message, RequestedSchema: rpc.RequestedSchema{ Type: rpc.RequestedSchemaTypeObject, @@ -732,7 +730,7 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin if err := ui.session.assertElicitation(); err != nil { return "", false, err } - rpcResult, err := ui.session.RPC.Ui.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ Message: message, RequestedSchema: rpc.RequestedSchema{ Type: rpc.RequestedSchemaTypeObject, @@ -786,7 +784,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio prop.Default = &rpc.Content{String: &opts.Default} } } - rpcResult, err := ui.session.RPC.Ui.Elicitation(ctx, &rpc.SessionUIElicitationParams{ + rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.SessionUIElicitationParams{ Message: message, RequestedSchema: rpc.RequestedSchema{ Type: rpc.RequestedSchemaTypeObject, @@ -888,111 +886,74 @@ func (s *Session) processEvents() { // event consumer loop) so that a stalled handler does not block event delivery or // cause RPC deadlocks. func (s *Session) handleBroadcastEvent(event SessionEvent) { - switch event.Type { - case SessionEventTypeExternalToolRequested: - requestID := event.Data.RequestID - toolName := event.Data.ToolName - if requestID == nil || toolName == nil { - return - } - handler, ok := s.getToolHandler(*toolName) + switch d := event.Data.(type) { + case *ExternalToolRequestedData: + handler, ok := s.getToolHandler(d.ToolName) if !ok { return } - toolCallID := "" - if event.Data.ToolCallID != nil { - toolCallID = *event.Data.ToolCallID - } var tp, ts string - if event.Data.Traceparent != nil { - tp = *event.Data.Traceparent + if d.Traceparent != nil { + tp = *d.Traceparent } - if event.Data.Tracestate != nil { - ts = *event.Data.Tracestate + if d.Tracestate != nil { + ts = *d.Tracestate } - s.executeToolAndRespond(*requestID, *toolName, toolCallID, event.Data.Arguments, handler, tp, ts) + s.executeToolAndRespond(d.RequestID, d.ToolName, d.ToolCallID, d.Arguments, handler, tp, ts) - case SessionEventTypePermissionRequested: - requestID := event.Data.RequestID - if requestID == nil || event.Data.PermissionRequest == nil { - return - } - if event.Data.ResolvedByHook != nil && *event.Data.ResolvedByHook { + case *PermissionRequestedData: + if d.ResolvedByHook != nil && *d.ResolvedByHook { return // Already resolved by a permissionRequest hook; no client action needed. } handler := s.getPermissionHandler() if handler == nil { return } - s.executePermissionAndRespond(*requestID, *event.Data.PermissionRequest, handler) + s.executePermissionAndRespond(d.RequestID, d.PermissionRequest, handler) - case SessionEventTypeCommandExecute: - requestID := event.Data.RequestID - if requestID == nil { - return - } - commandName := "" - if event.Data.CommandName != nil { - commandName = *event.Data.CommandName - } - command := "" - if event.Data.Command != nil { - command = *event.Data.Command - } - args := "" - if event.Data.Args != nil { - args = *event.Data.Args - } - s.executeCommandAndRespond(*requestID, commandName, command, args) + case *CommandExecuteData: + s.executeCommandAndRespond(d.RequestID, d.CommandName, d.Command, d.Args) - case SessionEventTypeElicitationRequested: - requestID := event.Data.RequestID - if requestID == nil { - return - } + case *ElicitationRequestedData: handler := s.getElicitationHandler() if handler == nil { return } - message := "" - if event.Data.Message != nil { - message = *event.Data.Message - } var requestedSchema map[string]any - if event.Data.RequestedSchema != nil { + if d.RequestedSchema != nil { requestedSchema = map[string]any{ - "type": string(event.Data.RequestedSchema.Type), - "properties": event.Data.RequestedSchema.Properties, + "type": string(d.RequestedSchema.Type), + "properties": d.RequestedSchema.Properties, } - if len(event.Data.RequestedSchema.Required) > 0 { - requestedSchema["required"] = event.Data.RequestedSchema.Required + if len(d.RequestedSchema.Required) > 0 { + requestedSchema["required"] = d.RequestedSchema.Required } } mode := "" - if event.Data.Mode != nil { - mode = string(*event.Data.Mode) + if d.Mode != nil { + mode = string(*d.Mode) } elicitationSource := "" - if event.Data.ElicitationSource != nil { - elicitationSource = *event.Data.ElicitationSource + if d.ElicitationSource != nil { + elicitationSource = *d.ElicitationSource } url := "" - if event.Data.URL != nil { - url = *event.Data.URL + if d.URL != nil { + url = *d.URL } s.handleElicitationRequest(ElicitationContext{ SessionID: s.SessionID, - Message: message, + Message: d.Message, RequestedSchema: requestedSchema, Mode: mode, ElicitationSource: elicitationSource, URL: url, - }, *requestID) + }, d.RequestID) - case SessionEventTypeCapabilitiesChanged: - if event.Data.UI != nil && event.Data.UI.Elicitation != nil { + case *CapabilitiesChangedData: + if d.UI != nil && d.UI.Elicitation != nil { s.setCapabilities(&SessionCapabilities{ - UI: &UICapabilities{Elicitation: *event.Data.UI.Elicitation}, + UI: &UICapabilities{Elicitation: *d.UI.Elicitation}, }) } } @@ -1117,8 +1078,8 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques // return // } // for _, event := range events { -// if event.Type == "assistant.message" { -// fmt.Println("Assistant:", event.Data.Content) +// if d, ok := event.Data.(*copilot.AssistantMessageData); ok { +// fmt.Println("Assistant:", d.Content) // } // } func (s *Session) GetMessages(ctx context.Context) ([]SessionEvent, error) { diff --git a/go/session_test.go b/go/session_test.go index 30b29e7a4..7f22028db 100644 --- a/go/session_test.go +++ b/go/session_test.go @@ -402,8 +402,8 @@ func TestSession_Capabilities(t *testing.T) { elicitTrue := true session.dispatchEvent(SessionEvent{ Type: SessionEventTypeCapabilitiesChanged, - Data: Data{ - UI: &UI{Elicitation: &elicitTrue}, + Data: &CapabilitiesChangedData{ + UI: &CapabilitiesChangedDataUI{Elicitation: &elicitTrue}, }, }) @@ -419,8 +419,8 @@ func TestSession_Capabilities(t *testing.T) { elicitFalse := false session.dispatchEvent(SessionEvent{ Type: SessionEventTypeCapabilitiesChanged, - Data: Data{ - UI: &UI{Elicitation: &elicitFalse}, + Data: &CapabilitiesChangedData{ + UI: &CapabilitiesChangedDataUI{Elicitation: &elicitFalse}, }, }) diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 5f061fbd4..101702f18 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -12,6 +12,7 @@ import type { JSONSchema7 } from "json-schema"; import { FetchingJSONSchemaStore, InputData, JSONSchemaInput, quicktype } from "quicktype-core"; import { promisify } from "util"; import { + EXCLUDED_EVENT_TYPES, getApiSchemaPath, getSessionEventsSchemaPath, isNodeFullyExperimental, @@ -27,7 +28,7 @@ const execFileAsync = promisify(execFile); // ── Utilities ─────────────────────────────────────────────────────────────── // Go initialisms that should be all-caps -const goInitialisms = new Set(["id", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc"]); +const goInitialisms = new Set(["id", "ui", "uri", "url", "api", "http", "https", "json", "xml", "html", "css", "sql", "ssh", "tcp", "udp", "ip", "rpc", "mime"]); function toPascalCase(s: string): string { return s @@ -137,34 +138,651 @@ function collectRpcMethods(node: Record): RpcMethod[] { return results; } -// ── Session Events ────────────────────────────────────────────────────────── +// ── Session Events (custom codegen — per-event-type data structs) ─────────── -async function generateSessionEvents(schemaPath?: string): Promise { - console.log("Go: generating session-events..."); +interface GoEventVariant { + typeName: string; + dataClassName: string; + dataSchema: JSONSchema7; + dataDescription?: string; +} - const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); - const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; - const resolvedSchema = (schema.definitions?.SessionEvent as JSONSchema7) || schema; - const processed = postProcessSchema(resolvedSchema); +interface GoCodegenCtx { + structs: string[]; + enums: string[]; + enumsByValues: Map; // sorted-values-key → enumName + generatedNames: Set; +} - const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - await schemaInput.addSource({ name: "SessionEvent", schema: JSON.stringify(processed) }); +function extractGoEventVariants(schema: JSONSchema7): GoEventVariant[] { + const sessionEvent = schema.definitions?.SessionEvent as JSONSchema7; + if (!sessionEvent?.anyOf) throw new Error("Schema must have SessionEvent definition with anyOf"); + + return (sessionEvent.anyOf as JSONSchema7[]) + .map((variant) => { + if (typeof variant !== "object" || !variant.properties) throw new Error("Invalid variant"); + const typeSchema = variant.properties.type as JSONSchema7; + const typeName = typeSchema?.const as string; + if (!typeName) throw new Error("Variant must have type.const"); + const dataSchema = (variant.properties.data as JSONSchema7) || {}; + return { + typeName, + dataClassName: `${toPascalCase(typeName)}Data`, + dataSchema, + dataDescription: dataSchema.description, + }; + }) + .filter((v) => !EXCLUDED_EVENT_TYPES.has(v.typeName)); +} - const inputData = new InputData(); - inputData.addInput(schemaInput); +/** + * Find a const-valued discriminator property shared by all anyOf variants. + */ +function findGoDiscriminator( + variants: JSONSchema7[] +): { property: string; mapping: Map } | null { + if (variants.length === 0) return null; + const firstVariant = variants[0]; + if (!firstVariant.properties) return null; + + for (const [propName, propSchema] of Object.entries(firstVariant.properties)) { + if (typeof propSchema !== "object") continue; + if ((propSchema as JSONSchema7).const === undefined) continue; + + const mapping = new Map(); + let valid = true; + for (const variant of variants) { + if (!variant.properties) { valid = false; break; } + const vp = variant.properties[propName]; + if (typeof vp !== "object" || (vp as JSONSchema7).const === undefined) { valid = false; break; } + mapping.set(String((vp as JSONSchema7).const), variant); + } + if (valid && mapping.size === variants.length) { + return { property: propName, mapping }; + } + } + return null; +} - const result = await quicktype({ - inputData, - lang: "go", - rendererOptions: { package: "copilot" }, - }); +/** + * Get or create a Go enum type, deduplicating by value set. + */ +function getOrCreateGoEnum( + enumName: string, + values: string[], + ctx: GoCodegenCtx, + description?: string +): string { + const valuesKey = [...values].sort().join("|"); + const existing = ctx.enumsByValues.get(valuesKey); + if (existing) return existing; + + const lines: string[] = []; + if (description) { + for (const line of description.split(/\r?\n/)) { + lines.push(`// ${line}`); + } + } + lines.push(`type ${enumName} string`); + lines.push(``); + lines.push(`const (`); + for (const value of values) { + const constSuffix = value + .split(/[-_.]/) + .map((w) => + goInitialisms.has(w.toLowerCase()) + ? w.toUpperCase() + : w.charAt(0).toUpperCase() + w.slice(1) + ) + .join(""); + lines.push(`\t${enumName}${constSuffix} ${enumName} = "${value}"`); + } + lines.push(`)`); + + ctx.enumsByValues.set(valuesKey, enumName); + ctx.enums.push(lines.join("\n")); + return enumName; +} + +/** + * Resolve a JSON Schema property to a Go type string. + * Emits nested struct/enum definitions into ctx as a side effect. + */ +function resolveGoPropertyType( + propSchema: JSONSchema7, + parentTypeName: string, + jsonPropName: string, + isRequired: boolean, + ctx: GoCodegenCtx +): string { + const nestedName = parentTypeName + toGoFieldName(jsonPropName); + + // Handle anyOf + if (propSchema.anyOf) { + const nonNull = (propSchema.anyOf as JSONSchema7[]).filter((s) => s.type !== "null"); + const hasNull = (propSchema.anyOf as JSONSchema7[]).some((s) => s.type === "null"); + + if (nonNull.length === 1) { + // anyOf [T, null] → nullable T + const innerType = resolveGoPropertyType(nonNull[0], parentTypeName, jsonPropName, true, ctx); + if (isRequired && !hasNull) return innerType; + // Pointer-wrap if not already a pointer, slice, or map + if (innerType.startsWith("*") || innerType.startsWith("[]") || innerType.startsWith("map[")) { + return innerType; + } + return `*${innerType}`; + } + + if (nonNull.length > 1) { + // Check for discriminated union + const disc = findGoDiscriminator(nonNull); + if (disc) { + emitGoFlatDiscriminatedUnion(nestedName, disc.property, disc.mapping, ctx, propSchema.description); + return isRequired && !hasNull ? nestedName : `*${nestedName}`; + } + // Non-discriminated multi-type union → any + return "any"; + } + } + + // Handle enum + if (propSchema.enum && Array.isArray(propSchema.enum)) { + const enumType = getOrCreateGoEnum(nestedName, propSchema.enum as string[], ctx, propSchema.description); + return isRequired ? enumType : `*${enumType}`; + } + + // Handle const (discriminator markers) — just use string + if (propSchema.const !== undefined) { + return isRequired ? "string" : "*string"; + } + + const type = propSchema.type; + const format = propSchema.format; + + // Handle type arrays like ["string", "null"] + if (Array.isArray(type)) { + const nonNullTypes = (type as string[]).filter((t) => t !== "null"); + if (nonNullTypes.length === 1) { + const inner = resolveGoPropertyType( + { ...propSchema, type: nonNullTypes[0] as JSONSchema7["type"] }, + parentTypeName, + jsonPropName, + true, + ctx + ); + if (inner.startsWith("*") || inner.startsWith("[]") || inner.startsWith("map[")) return inner; + return `*${inner}`; + } + } + + // Simple types + if (type === "string") { + if (format === "date-time") { + return isRequired ? "time.Time" : "*time.Time"; + } + return isRequired ? "string" : "*string"; + } + if (type === "number") return isRequired ? "float64" : "*float64"; + if (type === "integer") return isRequired ? "int64" : "*int64"; + if (type === "boolean") return isRequired ? "bool" : "*bool"; + + // Array type + if (type === "array") { + const items = propSchema.items as JSONSchema7 | undefined; + if (items) { + // Discriminated union items + if (items.anyOf) { + const itemVariants = (items.anyOf as JSONSchema7[]).filter((v) => v.type !== "null"); + const disc = findGoDiscriminator(itemVariants); + if (disc) { + const itemTypeName = nestedName + "Item"; + emitGoFlatDiscriminatedUnion(itemTypeName, disc.property, disc.mapping, ctx, items.description); + return `[]${itemTypeName}`; + } + } + const itemType = resolveGoPropertyType(items, parentTypeName, jsonPropName + "Item", true, ctx); + return `[]${itemType}`; + } + return "[]any"; + } + + // Object type + if (type === "object" || (propSchema.properties && !type)) { + if (propSchema.properties && Object.keys(propSchema.properties).length > 0) { + emitGoStruct(nestedName, propSchema, ctx); + return isRequired ? nestedName : `*${nestedName}`; + } + if (propSchema.additionalProperties) { + if ( + typeof propSchema.additionalProperties === "object" && + Object.keys(propSchema.additionalProperties as Record).length > 0 + ) { + const ap = propSchema.additionalProperties as JSONSchema7; + if (ap.type === "object" && ap.properties) { + emitGoStruct(nestedName + "Value", ap, ctx); + return `map[string]${nestedName}Value`; + } + const valueType = resolveGoPropertyType(ap, parentTypeName, jsonPropName + "Value", true, ctx); + return `map[string]${valueType}`; + } + return "map[string]any"; + } + // Empty object or untyped + return "any"; + } + + return "any"; +} + +/** + * Emit a Go struct definition from an object schema. + */ +function emitGoStruct( + typeName: string, + schema: JSONSchema7, + ctx: GoCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) return; + ctx.generatedNames.add(typeName); + + const required = new Set(schema.required || []); + const lines: string[] = []; + const desc = description || schema.description; + if (desc) { + for (const line of desc.split(/\r?\n/)) { + lines.push(`// ${line}`); + } + } + lines.push(`type ${typeName} struct {`); + + for (const [propName, propSchema] of Object.entries(schema.properties || {})) { + if (typeof propSchema !== "object") continue; + const prop = propSchema as JSONSchema7; + const isReq = required.has(propName); + const goName = toGoFieldName(propName); + const goType = resolveGoPropertyType(prop, typeName, propName, isReq, ctx); + const omit = isReq ? "" : ",omitempty"; + + if (prop.description) { + lines.push(`\t// ${prop.description}`); + } + lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); + } + + lines.push(`}`); + ctx.structs.push(lines.join("\n")); +} + +/** + * Emit a flat Go struct for a discriminated union (anyOf with const discriminator). + * Merges all variant properties into a single struct. + */ +function emitGoFlatDiscriminatedUnion( + typeName: string, + discriminatorProp: string, + mapping: Map, + ctx: GoCodegenCtx, + description?: string +): void { + if (ctx.generatedNames.has(typeName)) return; + ctx.generatedNames.add(typeName); + + // Collect all properties across variants, determining which are required in all + const allProps = new Map< + string, + { schema: JSONSchema7; requiredInAll: boolean } + >(); + + for (const [, variant] of mapping) { + const required = new Set(variant.required || []); + for (const [propName, propSchema] of Object.entries(variant.properties || {})) { + if (typeof propSchema !== "object") continue; + if (!allProps.has(propName)) { + allProps.set(propName, { + schema: propSchema as JSONSchema7, + requiredInAll: required.has(propName), + }); + } else { + const existing = allProps.get(propName)!; + if (!required.has(propName)) { + existing.requiredInAll = false; + } + } + } + } + + // Properties not present in all variants must be optional + const variantCount = mapping.size; + for (const [propName, info] of allProps) { + let presentCount = 0; + for (const [, variant] of mapping) { + if (variant.properties && propName in variant.properties) { + presentCount++; + } + } + if (presentCount < variantCount) { + info.requiredInAll = false; + } + } + + // Discriminator field: generate an enum from the const values + const discGoName = toGoFieldName(discriminatorProp); + const discValues = [...mapping.keys()]; + const discEnumName = getOrCreateGoEnum( + typeName + discGoName, + discValues, + ctx, + `${discGoName} discriminator for ${typeName}.` + ); + + const lines: string[] = []; + if (description) { + for (const line of description.split(/\r?\n/)) { + lines.push(`// ${line}`); + } + } + lines.push(`type ${typeName} struct {`); + + // Emit discriminator field first + lines.push(`\t// ${discGoName} discriminator`); + lines.push(`\t${discGoName} ${discEnumName} \`json:"${discriminatorProp}"\``); + + // Emit remaining fields + for (const [propName, info] of allProps) { + if (propName === discriminatorProp) continue; + const goName = toGoFieldName(propName); + const goType = resolveGoPropertyType(info.schema, typeName, propName, info.requiredInAll, ctx); + const omit = info.requiredInAll ? "" : ",omitempty"; + if (info.schema.description) { + lines.push(`\t// ${info.schema.description}`); + } + lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); + } + + lines.push(`}`); + ctx.structs.push(lines.join("\n")); +} + +/** + * Generate the complete Go session-events file content. + */ +function generateGoSessionEventsCode(schema: JSONSchema7): string { + const variants = extractGoEventVariants(schema); + const ctx: GoCodegenCtx = { + structs: [], + enums: [], + enumsByValues: new Map(), + generatedNames: new Set(), + }; + + // Generate per-event data structs + const dataStructs: string[] = []; + for (const variant of variants) { + const required = new Set(variant.dataSchema.required || []); + const lines: string[] = []; + + if (variant.dataDescription) { + for (const line of variant.dataDescription.split(/\r?\n/)) { + lines.push(`// ${line}`); + } + } else { + lines.push(`// ${variant.dataClassName} holds the payload for ${variant.typeName} events.`); + } + lines.push(`type ${variant.dataClassName} struct {`); + + for (const [propName, propSchema] of Object.entries(variant.dataSchema.properties || {})) { + if (typeof propSchema !== "object") continue; + const prop = propSchema as JSONSchema7; + const isReq = required.has(propName); + const goName = toGoFieldName(propName); + const goType = resolveGoPropertyType(prop, variant.dataClassName, propName, isReq, ctx); + const omit = isReq ? "" : ",omitempty"; + + if (prop.description) { + lines.push(`\t// ${prop.description}`); + } + lines.push(`\t${goName} ${goType} \`json:"${propName}${omit}"\``); + } + + lines.push(`}`); + lines.push(``); + lines.push(`func (*${variant.dataClassName}) sessionEventData() {}`); + + dataStructs.push(lines.join("\n")); + } + + // Generate SessionEventType enum + const eventTypeEnum: string[] = []; + eventTypeEnum.push(`// SessionEventType identifies the kind of session event.`); + eventTypeEnum.push(`type SessionEventType string`); + eventTypeEnum.push(``); + eventTypeEnum.push(`const (`); + for (const variant of variants) { + const constName = + "SessionEventType" + + variant.typeName + .split(/[._]/) + .map((w) => + goInitialisms.has(w.toLowerCase()) + ? w.toUpperCase() + : w.charAt(0).toUpperCase() + w.slice(1) + ) + .join(""); + eventTypeEnum.push(`\t${constName} SessionEventType = "${variant.typeName}"`); + } + eventTypeEnum.push(`)`); + + // Assemble file + const out: string[] = []; + out.push(`// AUTO-GENERATED FILE - DO NOT EDIT`); + out.push(`// Generated from: session-events.schema.json`); + out.push(``); + out.push(`package copilot`); + out.push(``); + + // Imports — time is always needed for SessionEvent.Timestamp + out.push(`import (`); + out.push(`\t"encoding/json"`); + out.push(`\t"time"`); + out.push(`)`); + out.push(``); + + // SessionEventData interface + out.push(`// SessionEventData is the interface implemented by all per-event data types.`); + out.push(`type SessionEventData interface {`); + out.push(`\tsessionEventData()`); + out.push(`}`); + out.push(``); + + // RawSessionEventData for unknown event types + out.push(`// RawSessionEventData holds unparsed JSON data for unrecognized event types.`); + out.push(`type RawSessionEventData struct {`); + out.push(`\tRaw json.RawMessage`); + out.push(`}`); + out.push(``); + out.push(`func (RawSessionEventData) sessionEventData() {}`); + out.push(``); + out.push(`// MarshalJSON returns the original raw JSON so round-tripping preserves the payload.`); + out.push(`func (r RawSessionEventData) MarshalJSON() ([]byte, error) { return r.Raw, nil }`); + out.push(``); + + // SessionEvent struct + out.push(`// SessionEvent represents a single session event with a typed data payload.`); + out.push(`type SessionEvent struct {`); + out.push(`\t// Unique event identifier (UUID v4), generated when the event is emitted.`); + out.push(`\tID string \`json:"id"\``); + out.push(`\t// ISO 8601 timestamp when the event was created.`); + out.push(`\tTimestamp time.Time \`json:"timestamp"\``); + // parentId: string or null + out.push(`\t// ID of the preceding event in the session. Null for the first event.`); + out.push(`\tParentID *string \`json:"parentId"\``); + out.push(`\t// When true, the event is transient and not persisted.`); + out.push(`\tEphemeral *bool \`json:"ephemeral,omitempty"\``); + out.push(`\t// The event type discriminator.`); + out.push(`\tType SessionEventType \`json:"type"\``); + out.push(`\t// Typed event payload. Use a type switch to access per-event fields.`); + out.push(`\tData SessionEventData \`json:"-"\``); + out.push(`}`); + out.push(``); + + // UnmarshalSessionEvent + out.push(`// UnmarshalSessionEvent parses JSON bytes into a SessionEvent.`); + out.push(`func UnmarshalSessionEvent(data []byte) (SessionEvent, error) {`); + out.push(`\tvar r SessionEvent`); + out.push(`\terr := json.Unmarshal(data, &r)`); + out.push(`\treturn r, err`); + out.push(`}`); + out.push(``); + + // Marshal + out.push(`// Marshal serializes the SessionEvent to JSON.`); + out.push(`func (r *SessionEvent) Marshal() ([]byte, error) {`); + out.push(`\treturn json.Marshal(r)`); + out.push(`}`); + out.push(``); + + // Custom UnmarshalJSON + out.push(`func (e *SessionEvent) UnmarshalJSON(data []byte) error {`); + out.push(`\ttype rawEvent struct {`); + out.push(`\t\tID string \`json:"id"\``); + out.push(`\t\tTimestamp time.Time \`json:"timestamp"\``); + out.push(`\t\tParentID *string \`json:"parentId"\``); + out.push(`\t\tEphemeral *bool \`json:"ephemeral,omitempty"\``); + out.push(`\t\tType SessionEventType \`json:"type"\``); + out.push(`\t\tData json.RawMessage \`json:"data"\``); + out.push(`\t}`); + out.push(`\tvar raw rawEvent`); + out.push(`\tif err := json.Unmarshal(data, &raw); err != nil {`); + out.push(`\t\treturn err`); + out.push(`\t}`); + out.push(`\te.ID = raw.ID`); + out.push(`\te.Timestamp = raw.Timestamp`); + out.push(`\te.ParentID = raw.ParentID`); + out.push(`\te.Ephemeral = raw.Ephemeral`); + out.push(`\te.Type = raw.Type`); + out.push(``); + out.push(`\tswitch raw.Type {`); + for (const variant of variants) { + const constName = + "SessionEventType" + + variant.typeName + .split(/[._]/) + .map((w) => + goInitialisms.has(w.toLowerCase()) + ? w.toUpperCase() + : w.charAt(0).toUpperCase() + w.slice(1) + ) + .join(""); + out.push(`\tcase ${constName}:`); + out.push(`\t\tvar d ${variant.dataClassName}`); + out.push(`\t\tif err := json.Unmarshal(raw.Data, &d); err != nil {`); + out.push(`\t\t\treturn err`); + out.push(`\t\t}`); + out.push(`\t\te.Data = &d`); + } + out.push(`\tdefault:`); + out.push(`\t\te.Data = &RawSessionEventData{Raw: raw.Data}`); + out.push(`\t}`); + out.push(`\treturn nil`); + out.push(`}`); + out.push(``); + + // Custom MarshalJSON + out.push(`func (e SessionEvent) MarshalJSON() ([]byte, error) {`); + out.push(`\ttype rawEvent struct {`); + out.push(`\t\tID string \`json:"id"\``); + out.push(`\t\tTimestamp time.Time \`json:"timestamp"\``); + out.push(`\t\tParentID *string \`json:"parentId"\``); + out.push(`\t\tEphemeral *bool \`json:"ephemeral,omitempty"\``); + out.push(`\t\tType SessionEventType \`json:"type"\``); + out.push(`\t\tData any \`json:"data"\``); + out.push(`\t}`); + out.push(`\treturn json.Marshal(rawEvent{`); + out.push(`\t\tID: e.ID,`); + out.push(`\t\tTimestamp: e.Timestamp,`); + out.push(`\t\tParentID: e.ParentID,`); + out.push(`\t\tEphemeral: e.Ephemeral,`); + out.push(`\t\tType: e.Type,`); + out.push(`\t\tData: e.Data,`); + out.push(`\t})`); + out.push(`}`); + out.push(``); + + // Event type enum + out.push(eventTypeEnum.join("\n")); + out.push(``); + + // Per-event data structs + for (const ds of dataStructs) { + out.push(ds); + out.push(``); + } - const banner = `// AUTO-GENERATED FILE - DO NOT EDIT -// Generated from: session-events.schema.json + // Nested structs + for (const s of ctx.structs) { + out.push(s); + out.push(``); + } + + // Enums + for (const e of ctx.enums) { + out.push(e); + out.push(``); + } + + // Type aliases for types referenced by non-generated SDK code under their short names. + const TYPE_ALIASES: Record = { + PermissionRequest: "PermissionRequestedDataPermissionRequest", + PermissionRequestKind: "PermissionRequestedDataPermissionRequestKind", + PermissionRequestCommand: "PermissionRequestedDataPermissionRequestCommandsItem", + PossibleURL: "PermissionRequestedDataPermissionRequestPossibleUrlsItem", + Attachment: "UserMessageDataAttachmentsItem", + AttachmentType: "UserMessageDataAttachmentsItemType", + }; + const CONST_ALIASES: Record = { + AttachmentTypeFile: "UserMessageDataAttachmentsItemTypeFile", + AttachmentTypeDirectory: "UserMessageDataAttachmentsItemTypeDirectory", + AttachmentTypeSelection: "UserMessageDataAttachmentsItemTypeSelection", + AttachmentTypeGithubReference: "UserMessageDataAttachmentsItemTypeGithubReference", + AttachmentTypeBlob: "UserMessageDataAttachmentsItemTypeBlob", + PermissionRequestKindShell: "PermissionRequestedDataPermissionRequestKindShell", + PermissionRequestKindWrite: "PermissionRequestedDataPermissionRequestKindWrite", + PermissionRequestKindRead: "PermissionRequestedDataPermissionRequestKindRead", + PermissionRequestKindMcp: "PermissionRequestedDataPermissionRequestKindMcp", + PermissionRequestKindURL: "PermissionRequestedDataPermissionRequestKindURL", + PermissionRequestKindMemory: "PermissionRequestedDataPermissionRequestKindMemory", + PermissionRequestKindCustomTool: "PermissionRequestedDataPermissionRequestKindCustomTool", + PermissionRequestKindHook: "PermissionRequestedDataPermissionRequestKindHook", + }; + out.push(`// Type aliases for convenience.`); + out.push(`type (`); + for (const [alias, target] of Object.entries(TYPE_ALIASES)) { + out.push(`\t${alias} = ${target}`); + } + out.push(`)`); + out.push(``); + out.push(`// Constant aliases for convenience.`); + out.push(`const (`); + for (const [alias, target] of Object.entries(CONST_ALIASES)) { + out.push(`\t${alias} = ${target}`); + } + out.push(`)`); + out.push(``); + + return out.join("\n"); +} + +async function generateSessionEvents(schemaPath?: string): Promise { + console.log("Go: generating session-events..."); + + const resolvedPath = schemaPath ?? (await getSessionEventsSchemaPath()); + const schema = JSON.parse(await fs.readFile(resolvedPath, "utf-8")) as JSONSchema7; + const processed = postProcessSchema(schema); -`; + const code = generateGoSessionEventsCode(processed); - const outPath = await writeGeneratedFile("go/generated_session_events.go", banner + postProcessEnumConstants(result.lines.join("\n"))); + const outPath = await writeGeneratedFile("go/generated_session_events.go", code); console.log(` ✓ ${outPath}`); await formatGoFile(outPath); diff --git a/test/scenarios/auth/byok-anthropic/go/main.go b/test/scenarios/auth/byok-anthropic/go/main.go index 048d20f6b..ae1ea92a0 100644 --- a/test/scenarios/auth/byok-anthropic/go/main.go +++ b/test/scenarios/auth/byok-anthropic/go/main.go @@ -58,7 +58,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/auth/byok-azure/go/main.go b/test/scenarios/auth/byok-azure/go/main.go index 03f3b9dcf..eece7a9cd 100644 --- a/test/scenarios/auth/byok-azure/go/main.go +++ b/test/scenarios/auth/byok-azure/go/main.go @@ -62,7 +62,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/auth/byok-ollama/go/main.go b/test/scenarios/auth/byok-ollama/go/main.go index b8b34c5b7..8232c63dc 100644 --- a/test/scenarios/auth/byok-ollama/go/main.go +++ b/test/scenarios/auth/byok-ollama/go/main.go @@ -54,7 +54,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/auth/byok-openai/go/main.go b/test/scenarios/auth/byok-openai/go/main.go index fc05c71b4..01d0b6da9 100644 --- a/test/scenarios/auth/byok-openai/go/main.go +++ b/test/scenarios/auth/byok-openai/go/main.go @@ -53,7 +53,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/auth/gh-app/go/main.go b/test/scenarios/auth/gh-app/go/main.go index d84d030cd..b19d21cbd 100644 --- a/test/scenarios/auth/gh-app/go/main.go +++ b/test/scenarios/auth/gh-app/go/main.go @@ -185,7 +185,9 @@ func main() { if err != nil { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/bundling/app-backend-to-server/go/main.go b/test/scenarios/bundling/app-backend-to-server/go/main.go index df2be62b9..d1fa1f898 100644 --- a/test/scenarios/bundling/app-backend-to-server/go/main.go +++ b/test/scenarios/bundling/app-backend-to-server/go/main.go @@ -80,8 +80,12 @@ func chatHandler(w http.ResponseWriter, r *http.Request) { return } - if response != nil && response.Data.Content != nil { - writeJSON(w, http.StatusOK, chatResponse{Response: *response.Data.Content}) + if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + writeJSON(w, http.StatusOK, chatResponse{Response: d.Content}) + } else { + writeJSON(w, http.StatusBadGateway, chatResponse{Error: "No response content from Copilot CLI"}) + } } else { writeJSON(w, http.StatusBadGateway, chatResponse{Error: "No response content from Copilot CLI"}) } diff --git a/test/scenarios/bundling/app-direct-server/go/main.go b/test/scenarios/bundling/app-direct-server/go/main.go index 8be7dd605..447e99043 100644 --- a/test/scenarios/bundling/app-direct-server/go/main.go +++ b/test/scenarios/bundling/app-direct-server/go/main.go @@ -40,7 +40,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/bundling/container-proxy/go/main.go b/test/scenarios/bundling/container-proxy/go/main.go index 8be7dd605..447e99043 100644 --- a/test/scenarios/bundling/container-proxy/go/main.go +++ b/test/scenarios/bundling/container-proxy/go/main.go @@ -40,7 +40,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/bundling/fully-bundled/go/main.go b/test/scenarios/bundling/fully-bundled/go/main.go index b8902fd99..8fab8510d 100644 --- a/test/scenarios/bundling/fully-bundled/go/main.go +++ b/test/scenarios/bundling/fully-bundled/go/main.go @@ -36,7 +36,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/callbacks/hooks/go/main.go b/test/scenarios/callbacks/hooks/go/main.go index 44e6e0240..ad69e55a1 100644 --- a/test/scenarios/callbacks/hooks/go/main.go +++ b/test/scenarios/callbacks/hooks/go/main.go @@ -76,9 +76,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} fmt.Println("\n--- Hook execution log ---") hookLogMu.Lock() diff --git a/test/scenarios/callbacks/permissions/go/main.go b/test/scenarios/callbacks/permissions/go/main.go index a09bbf21d..fbd33ffd6 100644 --- a/test/scenarios/callbacks/permissions/go/main.go +++ b/test/scenarios/callbacks/permissions/go/main.go @@ -56,9 +56,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} fmt.Println("\n--- Permission request log ---") for _, entry := range permissionLog { diff --git a/test/scenarios/callbacks/user-input/go/main.go b/test/scenarios/callbacks/user-input/go/main.go index 50eb65a23..044c977cf 100644 --- a/test/scenarios/callbacks/user-input/go/main.go +++ b/test/scenarios/callbacks/user-input/go/main.go @@ -56,9 +56,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} fmt.Println("\n--- User input log ---") for _, entry := range inputLog { diff --git a/test/scenarios/modes/default/go/main.go b/test/scenarios/modes/default/go/main.go index dd2b45d33..b0c44459f 100644 --- a/test/scenarios/modes/default/go/main.go +++ b/test/scenarios/modes/default/go/main.go @@ -35,9 +35,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Printf("Response: %s\n", *response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Printf("Response: %s\n", d.Content) +} +} fmt.Println("Default mode test complete") } diff --git a/test/scenarios/modes/minimal/go/main.go b/test/scenarios/modes/minimal/go/main.go index c3624b114..dc9ad0190 100644 --- a/test/scenarios/modes/minimal/go/main.go +++ b/test/scenarios/modes/minimal/go/main.go @@ -40,9 +40,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Printf("Response: %s\n", *response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Printf("Response: %s\n", d.Content) +} +} fmt.Println("Minimal mode test complete") } diff --git a/test/scenarios/prompts/attachments/go/main.go b/test/scenarios/prompts/attachments/go/main.go index 95eb2b4d0..b7f4d2859 100644 --- a/test/scenarios/prompts/attachments/go/main.go +++ b/test/scenarios/prompts/attachments/go/main.go @@ -56,7 +56,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/prompts/reasoning-effort/go/main.go b/test/scenarios/prompts/reasoning-effort/go/main.go index ccb4e5284..af5381263 100644 --- a/test/scenarios/prompts/reasoning-effort/go/main.go +++ b/test/scenarios/prompts/reasoning-effort/go/main.go @@ -41,8 +41,10 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println("Reasoning effort: low") - fmt.Printf("Response: %s\n", *response.Data.Content) + if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Println("Reasoning effort: low") + fmt.Printf("Response: %s\n", d.Content) + } } } diff --git a/test/scenarios/prompts/system-message/go/main.go b/test/scenarios/prompts/system-message/go/main.go index 074c9994b..a49d65d88 100644 --- a/test/scenarios/prompts/system-message/go/main.go +++ b/test/scenarios/prompts/system-message/go/main.go @@ -42,7 +42,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/sessions/concurrent-sessions/go/main.go b/test/scenarios/sessions/concurrent-sessions/go/main.go index ced915531..e399fedf7 100644 --- a/test/scenarios/sessions/concurrent-sessions/go/main.go +++ b/test/scenarios/sessions/concurrent-sessions/go/main.go @@ -67,8 +67,10 @@ func main() { if err != nil { log.Fatal(err) } - if resp != nil && resp.Data.Content != nil { - results[0] = result{label: "Session 1 (pirate)", content: *resp.Data.Content} + if resp != nil { + if d, ok := resp.Data.(*copilot.AssistantMessageData); ok { + results[0] = result{label: "Session 1 (pirate)", content: d.Content} + } } }() go func() { @@ -79,8 +81,10 @@ func main() { if err != nil { log.Fatal(err) } - if resp != nil && resp.Data.Content != nil { - results[1] = result{label: "Session 2 (robot)", content: *resp.Data.Content} + if resp != nil { + if d, ok := resp.Data.(*copilot.AssistantMessageData); ok { + results[1] = result{label: "Session 2 (robot)", content: d.Content} + } } }() wg.Wait() diff --git a/test/scenarios/sessions/infinite-sessions/go/main.go b/test/scenarios/sessions/infinite-sessions/go/main.go index 540f8f6b4..29871eacc 100644 --- a/test/scenarios/sessions/infinite-sessions/go/main.go +++ b/test/scenarios/sessions/infinite-sessions/go/main.go @@ -54,9 +54,11 @@ func main() { if err != nil { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Printf("Q: %s\n", prompt) - fmt.Printf("A: %s\n\n", *response.Data.Content) + if response != nil { + if d, ok := response.Data.(*copilot.AssistantMessageData); ok { + fmt.Printf("Q: %s\n", prompt) + fmt.Printf("A: %s\n\n", d.Content) + } } } diff --git a/test/scenarios/sessions/session-resume/go/main.go b/test/scenarios/sessions/session-resume/go/main.go index 2ba0b24bc..330fb6852 100644 --- a/test/scenarios/sessions/session-resume/go/main.go +++ b/test/scenarios/sessions/session-resume/go/main.go @@ -59,7 +59,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/sessions/streaming/go/main.go b/test/scenarios/sessions/streaming/go/main.go index 6243a1662..cd8a44801 100644 --- a/test/scenarios/sessions/streaming/go/main.go +++ b/test/scenarios/sessions/streaming/go/main.go @@ -43,8 +43,10 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} fmt.Printf("\nStreaming chunks received: %d\n", chunkCount) } diff --git a/test/scenarios/tools/custom-agents/go/main.go b/test/scenarios/tools/custom-agents/go/main.go index f2add8224..d1769ff2b 100644 --- a/test/scenarios/tools/custom-agents/go/main.go +++ b/test/scenarios/tools/custom-agents/go/main.go @@ -44,7 +44,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/tools/mcp-servers/go/main.go b/test/scenarios/tools/mcp-servers/go/main.go index a6e2e9c1f..d2ae5ab86 100644 --- a/test/scenarios/tools/mcp-servers/go/main.go +++ b/test/scenarios/tools/mcp-servers/go/main.go @@ -62,9 +62,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} if len(mcpServers) > 0 { keys := make([]string, 0, len(mcpServers)) diff --git a/test/scenarios/tools/no-tools/go/main.go b/test/scenarios/tools/no-tools/go/main.go index 62af3bcea..5d1aa872f 100644 --- a/test/scenarios/tools/no-tools/go/main.go +++ b/test/scenarios/tools/no-tools/go/main.go @@ -45,7 +45,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/tools/skills/go/main.go b/test/scenarios/tools/skills/go/main.go index 5652de329..b822377cc 100644 --- a/test/scenarios/tools/skills/go/main.go +++ b/test/scenarios/tools/skills/go/main.go @@ -49,9 +49,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} fmt.Println("\nSkill directories configured successfully") } diff --git a/test/scenarios/tools/tool-filtering/go/main.go b/test/scenarios/tools/tool-filtering/go/main.go index 851ca3111..e4a958be2 100644 --- a/test/scenarios/tools/tool-filtering/go/main.go +++ b/test/scenarios/tools/tool-filtering/go/main.go @@ -42,7 +42,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/tools/tool-overrides/go/main.go b/test/scenarios/tools/tool-overrides/go/main.go index 75b7698c0..8d5f6a756 100644 --- a/test/scenarios/tools/tool-overrides/go/main.go +++ b/test/scenarios/tools/tool-overrides/go/main.go @@ -47,7 +47,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/tools/virtual-filesystem/go/main.go b/test/scenarios/tools/virtual-filesystem/go/main.go index 39e3d910e..1618e661a 100644 --- a/test/scenarios/tools/virtual-filesystem/go/main.go +++ b/test/scenarios/tools/virtual-filesystem/go/main.go @@ -110,9 +110,11 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} // Dump the virtual filesystem to prove nothing touched disk fmt.Println("\n--- Virtual filesystem contents ---") diff --git a/test/scenarios/transport/reconnect/go/main.go b/test/scenarios/transport/reconnect/go/main.go index 493e9d258..f7f6cd152 100644 --- a/test/scenarios/transport/reconnect/go/main.go +++ b/test/scenarios/transport/reconnect/go/main.go @@ -37,9 +37,11 @@ func main() { log.Fatal(err) } - if response1 != nil && response1.Data.Content != nil { - fmt.Println(*response1.Data.Content) - } else { + if response1 != nil { +if d, ok := response1.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} else { log.Fatal("No response content received for session 1") } @@ -63,9 +65,11 @@ func main() { log.Fatal(err) } - if response2 != nil && response2.Data.Content != nil { - fmt.Println(*response2.Data.Content) - } else { + if response2 != nil { +if d, ok := response2.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} else { log.Fatal("No response content received for session 2") } diff --git a/test/scenarios/transport/stdio/go/main.go b/test/scenarios/transport/stdio/go/main.go index b8902fd99..8fab8510d 100644 --- a/test/scenarios/transport/stdio/go/main.go +++ b/test/scenarios/transport/stdio/go/main.go @@ -36,7 +36,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} } diff --git a/test/scenarios/transport/tcp/go/main.go b/test/scenarios/transport/tcp/go/main.go index 8be7dd605..447e99043 100644 --- a/test/scenarios/transport/tcp/go/main.go +++ b/test/scenarios/transport/tcp/go/main.go @@ -40,7 +40,9 @@ func main() { log.Fatal(err) } - if response != nil && response.Data.Content != nil { - fmt.Println(*response.Data.Content) - } + if response != nil { +if d, ok := response.Data.(*copilot.AssistantMessageData); ok { +fmt.Println(d.Content) +} +} }