From b39f0de7be4d5724b23320398b26d12195d18572 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Fri, 17 Apr 2026 16:26:33 +0100 Subject: [PATCH 01/10] Update sessionFs types for structured error contract and add workspace tests - Regenerate rpc.ts from updated api.schema.json with structured error type (error?: { code: 'ENOENT' | 'UNKNOWN', message?: string }) on all sessionFs results - Update createTestSessionFsHandler to catch fs errors and return error objects instead of throwing (matches the new RPC contract) - Add E2E tests for workspace metadata and plan.md written via sessionFs - Add test snapshots for new workspace tests Depends on: github/copilot-agent-runtime#6479 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/generated/rpc.ts | 184 ++++++- nodejs/src/generated/session-events.ts | 516 ++++-------------- nodejs/test/e2e/session_fs.test.ts | 149 +++-- .../should_persist_plan_md_via_sessionfs.yaml | 10 + ...rite_workspace_metadata_via_sessionfs.yaml | 10 + 5 files changed, 416 insertions(+), 453 deletions(-) create mode 100644 test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml create mode 100644 test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index dedfa8068..dca2c76d0 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -642,6 +642,22 @@ export interface PermissionRequestResult { */ success: boolean; } +/** + * Describes a filesystem error. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsError". + */ +export interface SessionFsError { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} export interface PingResult { /** @@ -1176,7 +1192,6 @@ export interface WorkspacesGetWorkspaceResult { mc_session_id?: string; mc_last_event_id?: string; session_sync_level?: "local" | "user" | "repo_and_user"; - pr_create_sync_dismissed?: boolean; chronicle_sync_dismissed?: boolean; } | null; } @@ -1767,6 +1782,20 @@ export interface SessionFsReadFileResult { * File content as UTF-8 string */ content: string; + error?: SessionFsError1; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError1 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; } export interface SessionFsReadFileRequest { @@ -1780,6 +1809,23 @@ export interface SessionFsReadFileRequest { path: string; } +export interface SessionFsWriteFileResult { + error?: SessionFsError2; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError2 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsWriteFileRequest { /** * Target session identifier @@ -1799,6 +1845,23 @@ export interface SessionFsWriteFileRequest { mode?: number; } +export interface SessionFsAppendFileResult { + error?: SessionFsError3; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError3 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsAppendFileRequest { /** * Target session identifier @@ -1857,6 +1920,20 @@ export interface SessionFsStatResult { * ISO 8601 timestamp of creation */ birthtime: string; + error?: SessionFsError4; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError4 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; } export interface SessionFsStatRequest { @@ -1870,6 +1947,23 @@ export interface SessionFsStatRequest { path: string; } +export interface SessionFsMkdirResult { + error?: SessionFsError5; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError5 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsMkdirRequest { /** * Target session identifier @@ -1894,6 +1988,20 @@ export interface SessionFsReaddirResult { * Entry names in the directory */ entries: string[]; + error?: SessionFsError6; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError6 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; } export interface SessionFsReaddirRequest { @@ -1921,6 +2029,20 @@ export interface SessionFsReaddirWithTypesResult { */ type: "file" | "directory"; }[]; + error?: SessionFsError7; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError7 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; } export interface SessionFsReaddirWithTypesRequest { @@ -1934,6 +2056,23 @@ export interface SessionFsReaddirWithTypesRequest { path: string; } +export interface SessionFsRmResult { + error?: SessionFsError8; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError8 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsRmRequest { /** * Target session identifier @@ -1953,6 +2092,23 @@ export interface SessionFsRmRequest { force?: boolean; } +export interface SessionFsRenameResult { + error?: SessionFsError9; +} +/** + * Describes a filesystem error. + */ +export interface SessionFsError9 { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsRenameRequest { /** * Target session identifier @@ -2324,6 +2480,22 @@ export interface $Defs_PermissionRequestResult { */ success: boolean; } +/** + * Describes a filesystem error. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "$defs_SessionFsError". + */ +export interface $Defs_SessionFsError { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { @@ -2519,15 +2691,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** Handler for `sessionFs` client session API methods. */ export interface SessionFsHandler { readFile(params: SessionFsReadFileRequest): Promise; - writeFile(params: SessionFsWriteFileRequest): Promise; - appendFile(params: SessionFsAppendFileRequest): Promise; + writeFile(params: SessionFsWriteFileRequest): Promise; + appendFile(params: SessionFsAppendFileRequest): Promise; exists(params: SessionFsExistsRequest): Promise; stat(params: SessionFsStatRequest): Promise; - mkdir(params: SessionFsMkdirRequest): Promise; + mkdir(params: SessionFsMkdirRequest): Promise; readdir(params: SessionFsReaddirRequest): Promise; readdirWithTypes(params: SessionFsReaddirWithTypesRequest): Promise; - rm(params: SessionFsRmRequest): Promise; - rename(params: SessionFsRenameRequest): Promise; + rm(params: SessionFsRmRequest): Promise; + rename(params: SessionFsRenameRequest): Promise; } /** All client session API handler groups. */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index d2de8d250..2a5b08b21 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -21,10 +21,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.start"; /** * Session initialization metadata including context and configuration @@ -58,7 +54,39 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - context?: WorkingDirectoryContext; + /** + * Working directory and git context at session start + */ + context?: { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; + }; /** * Whether the session was already in use by another client at start time */ @@ -86,10 +114,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.resume"; /** * Session resume metadata including current context and event count @@ -111,7 +135,39 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - context?: WorkingDirectoryContext1; + /** + * Updated working directory and git context at resume time + */ + context?: { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; + }; /** * Whether the session was already in use by another client at resume time */ @@ -139,10 +195,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.remote_steerable_changed"; /** * Notifies Mission Control that the session's remote steering capability has changed @@ -171,10 +223,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.error"; /** * Error details for timeline display including message and optional diagnostic information @@ -220,10 +268,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.idle"; /** * Payload indicating the session is idle with no background agents in flight @@ -249,10 +293,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.title_changed"; /** * Session title change payload containing the new display title @@ -276,10 +316,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.info"; /** * Informational message for timeline display with categorization @@ -316,10 +352,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.warning"; /** * Warning message for timeline display with categorization @@ -356,10 +388,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.model_change"; /** * Model change details including previous and new model identifiers @@ -400,10 +428,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.mode_changed"; /** * Agent mode change details including previous and new modes @@ -436,10 +460,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.plan_changed"; /** * Plan file operation details indicating what changed @@ -468,10 +488,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.workspace_file_changed"; /** * Workspace file change details including path and operation type @@ -504,10 +520,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.handoff"; /** * Session handoff metadata including source, context, and repository information @@ -573,10 +585,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.truncation"; /** * Conversation truncation statistics including token counts and removed content metrics @@ -630,10 +638,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.snapshot_rewind"; /** * Session rewind details including target event and count of removed events @@ -666,10 +670,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.shutdown"; /** * Session termination metrics including usage statistics, code changes, and shutdown reason @@ -796,12 +796,40 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + type: "session.context_changed"; /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + * Updated working directory and git context after the change */ - agentId?: string; - type: "session.context_changed"; - data: WorkingDirectoryContext2; + data: { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; + }; } | { /** @@ -817,10 +845,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.usage_info"; /** * Current context window usage statistics including token and message counts @@ -873,10 +897,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.compaction_start"; /** * Context window breakdown at the start of LLM-powered conversation compaction @@ -913,10 +933,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.compaction_complete"; /** * Conversation compaction results including success status, metrics, and optional error details @@ -1014,10 +1030,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.task_complete"; /** * Task completion notification with summary from the agent @@ -1050,10 +1062,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "user.message"; data: { /** @@ -1199,14 +1207,6 @@ export type SessionEvent = displayName?: string; } )[]; - /** - * Normalized document MIME types that were sent natively instead of through tagged_files XML - */ - supportedNativeDocumentMimeTypes?: string[]; - /** - * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit - */ - nativeDocumentPathFallbackPaths?: string[]; /** * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) */ @@ -1235,10 +1235,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "pending_messages.modified"; /** * Empty payload; the event signals that the pending message queue has changed @@ -1262,10 +1258,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.turn_start"; /** * Turn initialization metadata including identifier and interaction tracking @@ -1295,10 +1287,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.intent"; /** * Agent intent description for current activity or plan @@ -1327,10 +1315,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.reasoning"; /** * Assistant reasoning content for timeline display with complete thinking text @@ -1360,10 +1344,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.reasoning_delta"; /** * Streaming reasoning delta for incremental extended thinking updates @@ -1393,10 +1373,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.streaming_delta"; /** * Streaming response progress with cumulative byte count @@ -1425,10 +1401,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.message"; /** * Assistant response containing text content, optional tool requests, and interaction metadata @@ -1506,7 +1478,6 @@ export type SessionEvent = */ requestId?: string; /** - * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1526,10 +1497,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.message_delta"; /** * Streaming assistant message delta for incremental response updates @@ -1544,7 +1511,6 @@ export type SessionEvent = */ deltaContent: string; /** - * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1567,10 +1533,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.turn_end"; /** * Turn completion metadata including the turn identifier @@ -1596,10 +1558,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "assistant.usage"; /** * LLM API call usage metrics including tokens, costs, quotas, and billing information @@ -1658,7 +1616,6 @@ export type SessionEvent = */ providerCallId?: string; /** - * @deprecated * Parent tool call ID when this usage originates from a sub-agent */ parentToolCallId?: string; @@ -1754,10 +1711,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "abort"; /** * Turn abort information including the reason for termination @@ -1786,10 +1739,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "tool.user_requested"; /** * User-initiated tool invocation request with tool name and arguments @@ -1828,10 +1777,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "tool.execution_start"; /** * Tool execution startup details including MCP server information when applicable @@ -1860,7 +1805,6 @@ export type SessionEvent = */ mcpToolName?: string; /** - * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1880,10 +1824,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "tool.execution_partial_result"; /** * Streaming tool execution output for incremental result display @@ -1913,10 +1853,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "tool.execution_progress"; /** * Tool execution progress notification with status message @@ -1949,10 +1885,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "tool.execution_complete"; /** * Tool execution completion results including success status, detailed output, and error information @@ -2129,7 +2061,6 @@ export type SessionEvent = [k: string]: unknown; }; /** - * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -2152,10 +2083,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "skill.invoked"; /** * Skill invocation details including content, allowed tools, and plugin metadata @@ -2208,10 +2135,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "subagent.started"; /** * Sub-agent startup details including parent tool call and agent information @@ -2252,10 +2175,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "subagent.completed"; /** * Sub-agent completion details for successful execution @@ -2308,10 +2227,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "subagent.failed"; /** * Sub-agent failure details including error message and agent information @@ -2368,10 +2283,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "subagent.selected"; /** * Custom agent selection details including name and available tools @@ -2408,10 +2319,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "subagent.deselected"; /** * Empty payload; the event signals that the custom agent was deselected, returning to the default agent @@ -2435,10 +2342,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "hook.start"; /** * Hook invocation start details including type and input data @@ -2477,10 +2380,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "hook.end"; /** * Hook invocation completion details including output, success status, and error information @@ -2536,17 +2435,13 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "system.message"; /** - * System/developer instruction content with role and optional template metadata + * System or developer message content with role and optional template metadata */ data: { /** - * The system or developer prompt text sent as model input + * The system or developer prompt text */ content: string; /** @@ -2591,10 +2486,6 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "system.notification"; /** * System-generated notification for runtime events like background task completion @@ -2688,10 +2579,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "permission.requested"; /** * Permission request notification requiring client approval with request details @@ -2786,10 +2673,6 @@ export type SessionEvent = * Complete new file contents for newly created files */ newFileContents?: string; - /** - * Whether the UI can offer session-wide approval for file write operations - */ - canOfferSessionApproval: boolean; } | { /** @@ -2961,10 +2844,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "permission.completed"; /** * Permission request completion notification signaling UI dismissal @@ -3005,10 +2884,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "user_input.requested"; /** * User input request notification with question and optional predefined choices @@ -3050,10 +2925,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "user_input.completed"; /** * User input request completion with the user's response @@ -3087,10 +2958,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "elicitation.requested"; /** * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) @@ -3156,10 +3023,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "elicitation.completed"; /** * Elicitation request completion with the user's response @@ -3195,10 +3058,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "sampling.requested"; /** * Sampling request from an MCP server; contains the server name and a requestId for correlation @@ -3233,10 +3092,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "sampling.completed"; /** * Sampling request completion notification signaling UI dismissal @@ -3262,10 +3117,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "mcp.oauth_required"; /** * OAuth authentication request for an MCP server @@ -3312,10 +3163,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "mcp.oauth_completed"; /** * MCP OAuth request completion notification @@ -3341,10 +3188,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "external_tool.requested"; /** * External tool invocation request for client-side tool execution @@ -3396,10 +3239,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "external_tool.completed"; /** * External tool completion notification signaling UI dismissal @@ -3425,10 +3264,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "command.queued"; /** * Queued slash command dispatch request for client execution @@ -3458,10 +3293,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "command.execute"; /** * Registered command dispatch request routed to the owning client @@ -3499,10 +3330,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "command.completed"; /** * Queued command completion notification signaling UI dismissal @@ -3528,10 +3355,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "commands.changed"; /** * SDK command registration change notification @@ -3560,10 +3383,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "capabilities.changed"; /** * Session capability change notification @@ -3594,10 +3413,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "exit_plan_mode.requested"; /** * Plan approval request with plan content and available user actions @@ -3639,10 +3454,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "exit_plan_mode.completed"; /** * Plan mode exit completion with the user's approval decision and optional feedback @@ -3684,10 +3495,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.tools_updated"; data: { model: string; @@ -3707,10 +3514,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.background_tasks_changed"; data: {}; } @@ -3728,10 +3531,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.skills_loaded"; data: { /** @@ -3779,10 +3578,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.custom_agents_updated"; data: { /** @@ -3846,10 +3641,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.mcp_servers_loaded"; data: { /** @@ -3889,10 +3680,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.mcp_server_status_changed"; data: { /** @@ -3919,10 +3706,6 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; type: "session.extensions_loaded"; data: { /** @@ -3949,105 +3732,6 @@ export type SessionEvent = }; }; -/** - * Working directory and git context at session start - */ -export interface WorkingDirectoryContext { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; -} -/** - * Updated working directory and git context at resume time - */ -export interface WorkingDirectoryContext1 { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; -} -/** - * Updated working directory and git context after the change - */ -export interface WorkingDirectoryContext2 { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; -} export interface EmbeddedTextResourceContents { /** * URI identifying the resource diff --git a/nodejs/test/e2e/session_fs.test.ts b/nodejs/test/e2e/session_fs.test.ts index 8185a55be..fa74e10a4 100644 --- a/nodejs/test/e2e/session_fs.test.ts +++ b/nodejs/test/e2e/session_fs.test.ts @@ -123,6 +123,46 @@ describe("Session Fs", async () => { expect(fileContent).toBe(suppliedFileContent); }); + it("should write workspace metadata via sessionFs", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + createSessionFsHandler, + }); + + const msg = await session.sendAndWait({ prompt: "What is 7 * 8?" }); + expect(msg?.data.content).toContain("56"); + + // WorkspaceManager should have created workspace.yaml via sessionFs + const workspaceYamlPath = p(session.sessionId, "/session-state/workspace.yaml"); + await expect.poll(() => provider.exists(workspaceYamlPath)).toBe(true); + const yaml = await provider.readFile(workspaceYamlPath, "utf8"); + expect(yaml).toContain("id:"); + + // Checkpoint index should also exist + const indexPath = p(session.sessionId, "/session-state/checkpoints/index.md"); + await expect.poll(() => provider.exists(indexPath)).toBe(true); + + await session.disconnect(); + }); + + it("should persist plan.md via sessionFs", async () => { + const session = await client.createSession({ + onPermissionRequest: approveAll, + createSessionFsHandler, + }); + + // Write a plan via the session RPC + await session.sendAndWait({ prompt: "What is 2 + 3?" }); + await session.rpc.plan.update({ content: "# Test Plan\n\nThis is a test." }); + + const planPath = p(session.sessionId, "/session-state/plan.md"); + await expect.poll(() => provider.exists(planPath)).toBe(true); + const content = await provider.readFile(planPath, "utf8"); + expect(content).toContain("# Test Plan"); + + await session.disconnect(); + }); + it("should succeed with compaction while using sessionFs", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, @@ -181,58 +221,105 @@ function createTestSessionFsHandler( const sp = (sessionId: string, path: string) => `/${sessionId}${path.startsWith("/") ? path : "/" + path}`; + function mapError(err: unknown): { code: "ENOENT" | "UNKNOWN"; message?: string } { + const e = err as NodeJS.ErrnoException; + if (e.code === "ENOENT") return { code: "ENOENT", message: e.message }; + return { code: "UNKNOWN", message: e.message ?? String(err) }; + } + return { readFile: async ({ path }) => { - const content = await provider.readFile(sp(session.sessionId, path), "utf8"); - return { content: content as string }; + try { + const content = await provider.readFile(sp(session.sessionId, path), "utf8"); + return { content: content as string }; + } catch (err) { + return { content: "", error: mapError(err) }; + } }, writeFile: async ({ path, content }) => { - await provider.writeFile(sp(session.sessionId, path), content); + try { + await provider.writeFile(sp(session.sessionId, path), content); + return {}; + } catch (err) { + return { error: mapError(err) }; + } }, appendFile: async ({ path, content }) => { - await provider.appendFile(sp(session.sessionId, path), content); + try { + await provider.appendFile(sp(session.sessionId, path), content); + return {}; + } catch (err) { + return { error: mapError(err) }; + } }, exists: async ({ path }) => { return { exists: await provider.exists(sp(session.sessionId, path)) }; }, stat: async ({ path }) => { - const st = await provider.stat(sp(session.sessionId, path)); - return { - isFile: st.isFile(), - isDirectory: st.isDirectory(), - size: st.size, - mtime: new Date(st.mtimeMs).toISOString(), - birthtime: new Date(st.birthtimeMs).toISOString(), - }; + try { + const st = await provider.stat(sp(session.sessionId, path)); + return { + isFile: st.isFile(), + isDirectory: st.isDirectory(), + size: st.size, + mtime: new Date(st.mtimeMs).toISOString(), + birthtime: new Date(st.birthtimeMs).toISOString(), + }; + } catch (err) { + return { isFile: false, isDirectory: false, size: 0, mtime: new Date().toISOString(), birthtime: new Date().toISOString(), error: mapError(err) }; + } }, mkdir: async ({ path, recursive, mode }) => { - await provider.mkdir(sp(session.sessionId, path), { - recursive: recursive ?? false, - mode, - }); + try { + await provider.mkdir(sp(session.sessionId, path), { + recursive: recursive ?? false, + mode, + }); + return {}; + } catch (err) { + return { error: mapError(err) }; + } }, readdir: async ({ path }) => { - const entries = await provider.readdir(sp(session.sessionId, path)); - return { entries: entries as string[] }; + try { + const entries = await provider.readdir(sp(session.sessionId, path)); + return { entries: entries as string[] }; + } catch (err) { + return { entries: [], error: mapError(err) }; + } }, readdirWithTypes: async ({ path }) => { - const names = (await provider.readdir(sp(session.sessionId, path))) as string[]; - const entries = await Promise.all( - names.map(async (name) => { - const st = await provider.stat(sp(session.sessionId, `${path}/${name}`)); - return { - name, - type: st.isDirectory() ? ("directory" as const) : ("file" as const), - }; - }) - ); - return { entries }; + try { + const names = (await provider.readdir(sp(session.sessionId, path))) as string[]; + const entries = await Promise.all( + names.map(async (name) => { + const st = await provider.stat(sp(session.sessionId, `${path}/${name}`)); + return { + name, + type: st.isDirectory() ? ("directory" as const) : ("file" as const), + }; + }) + ); + return { entries }; + } catch (err) { + return { entries: [], error: mapError(err) }; + } }, rm: async ({ path }) => { - await provider.unlink(sp(session.sessionId, path)); + try { + await provider.unlink(sp(session.sessionId, path)); + return {}; + } catch (err) { + return { error: mapError(err) }; + } }, rename: async ({ src, dest }) => { - await provider.rename(sp(session.sessionId, src), sp(session.sessionId, dest)); + try { + await provider.rename(sp(session.sessionId, src), sp(session.sessionId, dest)); + return {}; + } catch (err) { + return { error: mapError(err) }; + } }, }; } diff --git a/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml new file mode 100644 index 000000000..5b0e81b22 --- /dev/null +++ b/test/snapshots/session_fs/should_persist_plan_md_via_sessionfs.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 2 + 3? + - role: assistant + content: 2 + 3 = 5 diff --git a/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml new file mode 100644 index 000000000..0a0325417 --- /dev/null +++ b/test/snapshots/session_fs/should_write_workspace_metadata_via_sessionfs.yaml @@ -0,0 +1,10 @@ +models: + - claude-sonnet-4.5 +conversations: + - messages: + - role: system + content: ${system} + - role: user + content: What is 7 * 8? + - role: assistant + content: 7 * 8 = 56 From 569aa349c56a9c14c598a4c44aa0bb5b331b96c6 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 14:19:38 +0100 Subject: [PATCH 02/10] Fix codegen: deduplicate $ ef siblings and $defs aliases Two issues caused duplicate type generation in json-schema-to-typescript: 1. $ ef objects with sibling keywords (e.g. description) were treated as new inline types instead of reusing the referenced definition. Fix: strip all sibling keywords from $ ef objects in normalizeSchemaForTypeScript. 2. withSharedDefinitions copies definitions into $defs when $defs is empty, then normalizeSchemaForTypeScript aliased collisions as $defs_X. Since definitions entries go through the full pipeline (title-stripping, ref-reuse) but $defs copies don't, they diverge. Fix: when a key exists in both, prefer the definitions entry and drop the $defs duplicate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/generated/rpc.ts | 961 ++-------------------------------- scripts/codegen/typescript.ts | 31 +- 2 files changed, 54 insertions(+), 938 deletions(-) diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index dca2c76d0..8d8f708c9 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -66,8 +66,6 @@ export type FilterMapping = * via the `definition` "SessionMode". */ export type SessionMode = "interactive" | "plan" | "autopilot"; - -export type UIElicitationFieldValue = string | number | boolean | string[]; /** * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) * @@ -76,6 +74,8 @@ export type UIElicitationFieldValue = string | number | boolean | string[]; */ export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; +export type UIElicitationFieldValue = string | number | boolean | string[]; + export type PermissionDecision = | { /** @@ -144,145 +144,6 @@ export type PermissionDecision = * via the `definition` "SessionLogLevel". */ export type SessionLogLevel = "info" | "warning" | "error"; -/** - * MCP server configuration (local/stdio or remote/http) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_McpServerConfig". - */ -export type $Defs_McpServerConfig = - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; - -export type $Defs_FilterMapping = - | { - [k: string]: "none" | "markdown" | "hidden_characters"; - } - | ("none" | "markdown" | "hidden_characters"); -/** - * The agent mode. Valid values: "interactive", "plan", "autopilot". - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_SessionMode". - */ -export type $Defs_SessionMode = "interactive" | "plan" | "autopilot"; -/** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponseAction". - */ -export type $Defs_UIElicitationResponseAction = "accept" | "decline" | "cancel"; - -export type $Defs_UIElicitationFieldValue = string | number | boolean | string[]; - -export type $Defs_PermissionDecision = - | { - /** - * The permission request was approved - */ - kind: "approved"; - } - | { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; -/** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_SessionLogLevel". - */ -export type $Defs_SessionLogLevel = "info" | "warning" | "error"; /** * Model capabilities and limits @@ -323,30 +184,13 @@ export interface ModelCapabilities { vision?: ModelCapabilitiesLimitsVision; }; } -/** - * Vision-specific limits - */ -export interface ModelCapabilitiesLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; -} /** * Vision-specific limits * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "ModelCapabilitiesLimitsVision". */ -export interface ModelCapabilitiesLimitsVision1 { +export interface ModelCapabilitiesLimitsVision { /** * MIME types the model accepts */ @@ -581,25 +425,16 @@ export interface UIElicitationArrayAnyOfField { * via the `definition` "UIElicitationResponse". */ export interface UIElicitationResponse { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; + action: UIElicitationResponseAction; content?: UIElicitationResponseContent; } -/** - * The form values submitted by the user (present when action is 'accept') - */ -export interface UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; -} /** * The form values submitted by the user (present when action is 'accept') * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "UIElicitationResponseContent". */ -export interface UIElicitationResponseContent1 { +export interface UIElicitationResponseContent { [k: string]: UIElicitationFieldValue; } @@ -608,17 +443,7 @@ export interface UIHandlePendingElicitationRequest { * The unique request ID from the elicitation.requested event */ requestId: string; - result: UIElicitationResponse1; -} -/** - * The elicitation response (accept with form values, decline, or cancel) - */ -export interface UIElicitationResponse1 { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent; + result: UIElicitationResponse; } export interface UIElicitationResult { @@ -694,7 +519,7 @@ export interface ModelList { * Display name */ name: string; - capabilities: ModelCapabilities1; + capabilities: ModelCapabilities; /** * Policy state (if applicable) */ @@ -727,42 +552,6 @@ export interface ModelList { defaultReasoningEffort?: string; }[]; } -/** - * Model capabilities and limits - */ -export interface ModelCapabilities1 { - /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision; - }; -} export interface ToolList { /** @@ -840,51 +629,7 @@ export interface McpConfigList { * All MCP servers from user config, keyed by name */ servers: { - /** - * MCP server configuration (local/stdio or remote/http) - */ - [k: string]: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; + [k: string]: McpServerConfig; }; } @@ -893,51 +638,7 @@ export interface McpConfigAddRequest { * Unique name for the MCP server */ name: string; - /** - * MCP server configuration (local/stdio or remote/http) - */ - config: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; + config: McpServerConfig; } export interface McpConfigUpdateRequest { @@ -945,51 +646,7 @@ export interface McpConfigUpdateRequest { * Name of the MCP server to update */ name: string; - /** - * MCP server configuration (local/stdio or remote/http) - */ - config: - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; + config: McpServerConfig; } export interface McpConfigRemoveRequest { @@ -1089,63 +746,23 @@ export interface ModelSwitchToRequest { * Reasoning effort level to use for the model */ reasoningEffort?: string; - modelCapabilities?: ModelCapabilitiesOverride1; + modelCapabilities?: ModelCapabilitiesOverride; } -/** - * Override individual model capabilities resolved by the runtime - */ -export interface ModelCapabilitiesOverride1 { + +export interface ModeSetRequest { + mode: SessionMode; +} + +export interface NameGetResult { /** - * Feature flags indicating what the model supports + * The session name, falling back to the auto-generated summary, or null if neither exists */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; + name: string | null; +} + +export interface NameSetRequest { /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; - }; -} - -export interface ModeSetRequest { - /** - * The agent mode. Valid values: "interactive", "plan", "autopilot". - */ - mode: "interactive" | "plan" | "autopilot"; -} - -export interface NameGetResult { - /** - * The session name, falling back to the auto-generated summary, or null if neither exists - */ - name: string | null; -} - -export interface NameSetRequest { - /** - * New session name (1–100 characters, trimmed of leading/trailing whitespace) + * New session name (1–100 characters, trimmed of leading/trailing whitespace) */ name: string; } @@ -1302,24 +919,7 @@ export interface AgentGetCurrentResult { /** @experimental */ export interface AgentSelectResult { - agent: AgentInfo1; -} -/** - * The newly selected custom agent - */ -export interface AgentInfo1 { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; + agent: AgentInfo; } /** @experimental */ @@ -1568,10 +1168,7 @@ export interface LogRequest { * Human-readable message */ message: string; - /** - * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". - */ - level?: "info" | "warning" | "error"; + level?: SessionLogLevel; /** * When true, the message is transient and not persisted to the session event log on disk */ @@ -1782,20 +1379,7 @@ export interface SessionFsReadFileResult { * File content as UTF-8 string */ content: string; - error?: SessionFsError1; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError1 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsReadFileRequest { @@ -1810,20 +1394,7 @@ export interface SessionFsReadFileRequest { } export interface SessionFsWriteFileResult { - error?: SessionFsError2; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError2 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsWriteFileRequest { @@ -1846,20 +1417,7 @@ export interface SessionFsWriteFileRequest { } export interface SessionFsAppendFileResult { - error?: SessionFsError3; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError3 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsAppendFileRequest { @@ -1920,20 +1478,7 @@ export interface SessionFsStatResult { * ISO 8601 timestamp of creation */ birthtime: string; - error?: SessionFsError4; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError4 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsStatRequest { @@ -1948,20 +1493,7 @@ export interface SessionFsStatRequest { } export interface SessionFsMkdirResult { - error?: SessionFsError5; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError5 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsMkdirRequest { @@ -1988,20 +1520,7 @@ export interface SessionFsReaddirResult { * Entry names in the directory */ entries: string[]; - error?: SessionFsError6; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError6 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsReaddirRequest { @@ -2029,20 +1548,7 @@ export interface SessionFsReaddirWithTypesResult { */ type: "file" | "directory"; }[]; - error?: SessionFsError7; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError7 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsReaddirWithTypesRequest { @@ -2057,20 +1563,7 @@ export interface SessionFsReaddirWithTypesRequest { } export interface SessionFsRmResult { - error?: SessionFsError8; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError8 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsRmRequest { @@ -2093,20 +1586,7 @@ export interface SessionFsRmRequest { } export interface SessionFsRenameResult { - error?: SessionFsError9; -} -/** - * Describes a filesystem error. - */ -export interface SessionFsError9 { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; + error?: SessionFsError; } export interface SessionFsRenameRequest { @@ -2123,379 +1603,6 @@ export interface SessionFsRenameRequest { */ dest: string; } -/** - * Model capabilities and limits - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilities". - */ -export interface $Defs_ModelCapabilities { - /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision2; - }; -} -/** - * Vision-specific limits - */ -export interface ModelCapabilitiesLimitsVision2 { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; -} -/** - * Vision-specific limits - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilitiesLimitsVision". - */ -export interface $Defs_ModelCapabilitiesLimitsVision { - /** - * MIME types the model accepts - */ - supported_media_types: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size: number; -} - -export interface $Defs_DiscoveredMcpServer { - /** - * Server name (config key) - */ - name: string; - /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) - */ - type?: "stdio" | "http" | "sse" | "memory"; - /** - * Configuration source - */ - source: "user" | "workspace" | "plugin" | "builtin"; - /** - * Whether the server is enabled (not in the disabled list) - */ - enabled: boolean; -} - -export interface $Defs_ServerSkillList { - /** - * All discovered skills across all sources - */ - skills: ServerSkill[]; -} - -export interface $Defs_ServerSkill { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled (based on global config) - */ - enabled: boolean; - /** - * Absolute path to the skill file - */ - path?: string; - /** - * The project path this skill belongs to (only for project/inherited skills) - */ - projectPath?: string; -} - -export interface $Defs_CurrentModel { - /** - * Currently active model identifier - */ - modelId?: string; -} -/** - * Override individual model capabilities resolved by the runtime - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_ModelCapabilitiesOverride". - */ -export interface $Defs_ModelCapabilitiesOverride { - /** - * Feature flags indicating what the model supports - */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; - /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; - }; -} - -export interface $Defs_AgentInfo { - /** - * Unique identifier of the custom agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of the agent's purpose - */ - description: string; -} - -export interface $Defs_McpServerList { - /** - * Configured MCP servers - */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: "user" | "workspace" | "plugin" | "builtin"; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; -} - -export interface $Defs_ToolCallResult { - /** - * Text result to send back to the LLM - */ - textResultForLlm: string; - /** - * Type of the tool result - */ - resultType?: string; - /** - * Error message if the tool call failed - */ - error?: string; - /** - * Telemetry data from tool execution - */ - toolTelemetry?: { - [k: string]: unknown; - }; -} - -export interface $Defs_HandleToolCallResult { - /** - * Whether the tool call result was handled successfully - */ - success: boolean; -} - -export interface $Defs_UIElicitationStringEnumField { - type: "string"; - description?: string; - enum: string[]; - enumNames?: string[]; - default?: string; -} - -export interface $Defs_UIElicitationStringOneOfField { - type: "string"; - description?: string; - oneOf: { - const: string; - }[]; - default?: string; -} - -export interface $Defs_UIElicitationArrayEnumField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; -} - -export interface $Defs_UIElicitationArrayAnyOfField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - }[]; - }; - default?: string[]; -} -/** - * The elicitation response (accept with form values, decline, or cancel) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponse". - */ -export interface $Defs_UIElicitationResponse { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent2; -} -/** - * The form values submitted by the user (present when action is 'accept') - */ -export interface UIElicitationResponseContent2 { - [k: string]: UIElicitationFieldValue; -} -/** - * The form values submitted by the user (present when action is 'accept') - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_UIElicitationResponseContent". - */ -export interface $Defs_UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; -} - -export interface $Defs_UIHandlePendingElicitationRequest { - /** - * The unique request ID from the elicitation.requested event - */ - requestId: string; - result: UIElicitationResponse2; -} -/** - * The elicitation response (accept with form values, decline, or cancel) - */ -export interface UIElicitationResponse2 { - /** - * The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - */ - action: "accept" | "decline" | "cancel"; - content?: UIElicitationResponseContent; -} - -export interface $Defs_UIElicitationResult { - /** - * Whether the response was accepted. False if the request was already resolved by another client. - */ - success: boolean; -} - -export interface $Defs_PermissionDecisionRequest { - /** - * Request ID of the pending permission request - */ - requestId: string; - result: PermissionDecision; -} - -export interface $Defs_PermissionRequestResult { - /** - * Whether the permission request was handled successfully - */ - success: boolean; -} -/** - * Describes a filesystem error. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "$defs_SessionFsError". - */ -export interface $Defs_SessionFsError { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; -} /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 1aba7384c..d9adaab57 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -143,15 +143,14 @@ function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { const draftDefinitionAliases = new Map(); for (const [key, value] of Object.entries(root.$defs ?? {})) { - let alias = key; - if (alias in definitions) { - alias = `$defs_${key}`; - while (alias in definitions) { - alias = `$defs_${alias}`; - } + if (key in definitions) { + // The definitions entry is authoritative (it went through the full pipeline). + // Drop the $defs duplicate and rewrite any $ref pointing at it to use definitions. + draftDefinitionAliases.set(key, key); + } else { + draftDefinitionAliases.set(key, key); + definitions[key] = value; } - draftDefinitionAliases.set(key, alias); - definitions[alias] = value; } root.definitions = definitions; @@ -169,9 +168,19 @@ function normalizeSchemaForTypeScript(schema: JSONSchema7): JSONSchema7 { Object.entries(value as Record).map(([key, child]) => [key, rewrite(child)]) ) as Record; - if (typeof rewritten.$ref === "string" && rewritten.$ref.startsWith("#/$defs/")) { - const definitionName = rewritten.$ref.slice("#/$defs/".length); - rewritten.$ref = `#/definitions/${draftDefinitionAliases.get(definitionName) ?? definitionName}`; + if (typeof rewritten.$ref === "string") { + if (rewritten.$ref.startsWith("#/$defs/")) { + const definitionName = rewritten.$ref.slice("#/$defs/".length); + rewritten.$ref = `#/definitions/${draftDefinitionAliases.get(definitionName) ?? definitionName}`; + } + // json-schema-to-typescript treats sibling keywords alongside $ref as a + // new inline type instead of reusing the referenced definition. Strip + // siblings so that $ref-only objects compile to a single shared type. + for (const key of Object.keys(rewritten)) { + if (key !== "$ref") { + delete rewritten[key]; + } + } } return rewritten; From 1d463156a1ce7cecdfc154dfceef958fe3128a5f Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 15:06:05 +0100 Subject: [PATCH 03/10] Codegen updates and fixes --- dotnet/src/Generated/Rpc.cs | 76 +- go/rpc/generated_rpc.go | 254 +---- nodejs/src/generated/rpc.ts | 30 +- nodejs/src/generated/session-events.ts | 450 +++++++-- nodejs/test/e2e/session_fs.test.ts | 20 +- python/copilot/generated/rpc.py | 1226 ++++++++---------------- scripts/codegen/go.ts | 21 +- scripts/codegen/python.ts | 22 +- scripts/codegen/utils.ts | 4 +- 9 files changed, 888 insertions(+), 1215 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 0c9880de6..61cc19fc1 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -738,10 +738,6 @@ public sealed class WorkspacesGetWorkspaceResultWorkspace [JsonPropertyName("session_sync_level")] public WorkspacesGetWorkspaceResultWorkspaceSessionSyncLevel? SessionSyncLevel { get; set; } - /// Gets or sets the pr_create_sync_dismissed value. - [JsonPropertyName("pr_create_sync_dismissed")] - public bool? PrCreateSyncDismissed { get; set; } - /// Gets or sets the chronicle_sync_dismissed value. [JsonPropertyName("chronicle_sync_dismissed")] public bool? ChronicleSyncDismissed { get; set; } @@ -1673,12 +1669,28 @@ internal sealed class SessionUsageGetMetricsRequest public string SessionId { get; set; } = string.Empty; } +/// Describes a filesystem error. +public sealed class SessionFsError +{ + /// Error classification. + [JsonPropertyName("code")] + public SessionFsErrorCode Code { get; set; } + + /// Free-form detail about the error, for logging/diagnostics. + [JsonPropertyName("message")] + public string? Message { get; set; } +} + /// RPC data type for SessionFsReadFile operations. public sealed class SessionFsReadFileResult { /// File content as UTF-8 string. [JsonPropertyName("content")] public string Content { get; set; } = string.Empty; + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReadFile operations. @@ -1778,6 +1790,10 @@ public sealed class SessionFsStatResult /// ISO 8601 timestamp of creation. [JsonPropertyName("birthtime")] public DateTimeOffset Birthtime { get; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsStat operations. @@ -1819,6 +1835,10 @@ public sealed class SessionFsReaddirResult /// Entry names in the directory. [JsonPropertyName("entries")] public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReaddir operations. @@ -1851,6 +1871,10 @@ public sealed class SessionFsReaddirWithTypesResult /// Directory entries with type information. [JsonPropertyName("entries")] public IList Entries { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } } /// RPC data type for SessionFsReaddirWithTypes operations. @@ -2162,6 +2186,19 @@ public enum ShellKillSignal } +/// Error classification. +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SessionFsErrorCode +{ + /// The ENOENT variant. + [JsonStringEnumMemberName("ENOENT")] + ENOENT, + /// The UNKNOWN variant. + [JsonStringEnumMemberName("UNKNOWN")] + UNKNOWN, +} + + /// Entry type. [JsonConverter(typeof(JsonStringEnumConverter))] public enum SessionFsReaddirWithTypesEntryType @@ -3068,23 +3105,23 @@ public interface ISessionFsHandler /// Handles "sessionFs.readFile". Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.writeFile". - Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); + Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.appendFile". - Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); + Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.exists". Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.stat". Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.mkdir". - Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); + Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdir". Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdirWithTypes". Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rm". - Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); + Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rename". - Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); + Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); } /// Provides all client session API handler groups for a session. @@ -3114,21 +3151,21 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsWriteFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.WriteFileAsync(request, cancellationToken); + return await handler.WriteFileAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsWriteFileMethod.Method, registerSessionFsWriteFileMethod.Target!, new JsonRpcMethodAttribute("sessionFs.writeFile") { UseSingleObjectParameterDeserialization = true }); - var registerSessionFsAppendFileMethod = (Func)(async (request, cancellationToken) => + var registerSessionFsAppendFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.AppendFileAsync(request, cancellationToken); + return await handler.AppendFileAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsAppendFileMethod.Method, registerSessionFsAppendFileMethod.Target!, new JsonRpcMethodAttribute("sessionFs.appendFile") { @@ -3154,11 +3191,11 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsMkdirMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.MkdirAsync(request, cancellationToken); + return await handler.MkdirAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsMkdirMethod.Method, registerSessionFsMkdirMethod.Target!, new JsonRpcMethodAttribute("sessionFs.mkdir") { @@ -3184,21 +3221,21 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func)(async (request, cancellationToken) => + var registerSessionFsRmMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.RmAsync(request, cancellationToken); + return await handler.RmAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsRmMethod.Method, registerSessionFsRmMethod.Target!, new JsonRpcMethodAttribute("sessionFs.rm") { UseSingleObjectParameterDeserialization = true }); - var registerSessionFsRenameMethod = (Func)(async (request, cancellationToken) => + var registerSessionFsRenameMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); - await handler.RenameAsync(request, cancellationToken); + return await handler.RenameAsync(request, cancellationToken); }); rpc.AddLocalRpcMethod(registerSessionFsRenameMethod.Method, registerSessionFsRenameMethod.Target!, new JsonRpcMethodAttribute("sessionFs.rename") { @@ -3282,6 +3319,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func; - writeFile(params: SessionFsWriteFileRequest): Promise; - appendFile(params: SessionFsAppendFileRequest): Promise; + writeFile(params: SessionFsWriteFileRequest): Promise; + appendFile(params: SessionFsAppendFileRequest): Promise; exists(params: SessionFsExistsRequest): Promise; stat(params: SessionFsStatRequest): Promise; - mkdir(params: SessionFsMkdirRequest): Promise; + mkdir(params: SessionFsMkdirRequest): Promise; readdir(params: SessionFsReaddirRequest): Promise; readdirWithTypes(params: SessionFsReaddirWithTypesRequest): Promise; - rm(params: SessionFsRmRequest): Promise; - rename(params: SessionFsRenameRequest): Promise; + rm(params: SessionFsRmRequest): Promise; + rename(params: SessionFsRenameRequest): Promise; } /** All client session API handler groups. */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 2a5b08b21..64bb0a95a 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -21,6 +21,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.start"; /** * Session initialization metadata including context and configuration @@ -54,39 +58,7 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - /** - * Working directory and git context at session start - */ - context?: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + context?: WorkingDirectoryContext; /** * Whether the session was already in use by another client at start time */ @@ -114,6 +86,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.resume"; /** * Session resume metadata including current context and event count @@ -135,39 +111,7 @@ export type SessionEvent = * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") */ reasoningEffort?: string; - /** - * Updated working directory and git context at resume time - */ - context?: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + context?: WorkingDirectoryContext; /** * Whether the session was already in use by another client at resume time */ @@ -195,6 +139,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.remote_steerable_changed"; /** * Notifies Mission Control that the session's remote steering capability has changed @@ -223,6 +171,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.error"; /** * Error details for timeline display including message and optional diagnostic information @@ -268,6 +220,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.idle"; /** * Payload indicating the session is idle with no background agents in flight @@ -293,6 +249,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.title_changed"; /** * Session title change payload containing the new display title @@ -316,6 +276,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.info"; /** * Informational message for timeline display with categorization @@ -352,6 +316,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.warning"; /** * Warning message for timeline display with categorization @@ -388,6 +356,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.model_change"; /** * Model change details including previous and new model identifiers @@ -428,6 +400,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mode_changed"; /** * Agent mode change details including previous and new modes @@ -460,6 +436,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.plan_changed"; /** * Plan file operation details indicating what changed @@ -488,6 +468,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.workspace_file_changed"; /** * Workspace file change details including path and operation type @@ -520,6 +504,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.handoff"; /** * Session handoff metadata including source, context, and repository information @@ -585,6 +573,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.truncation"; /** * Conversation truncation statistics including token counts and removed content metrics @@ -638,6 +630,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.snapshot_rewind"; /** * Session rewind details including target event and count of removed events @@ -670,6 +666,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.shutdown"; /** * Session termination metrics including usage statistics, code changes, and shutdown reason @@ -796,40 +796,12 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; - type: "session.context_changed"; /** - * Updated working directory and git context after the change + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. */ - data: { - /** - * Current working directory path - */ - cwd: string; - /** - * Root directory of the git repository, resolved via git rev-parse - */ - gitRoot?: string; - /** - * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) - */ - repository?: string; - /** - * Hosting platform type of the repository (github or ado) - */ - hostType?: "github" | "ado"; - /** - * Current git branch name - */ - branch?: string; - /** - * Head commit of current git branch at session start time - */ - headCommit?: string; - /** - * Base commit of current git branch at session start time - */ - baseCommit?: string; - }; + agentId?: string; + type: "session.context_changed"; + data: WorkingDirectoryContext; } | { /** @@ -845,6 +817,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.usage_info"; /** * Current context window usage statistics including token and message counts @@ -897,6 +873,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.compaction_start"; /** * Context window breakdown at the start of LLM-powered conversation compaction @@ -933,6 +913,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.compaction_complete"; /** * Conversation compaction results including success status, metrics, and optional error details @@ -1030,6 +1014,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.task_complete"; /** * Task completion notification with summary from the agent @@ -1062,6 +1050,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user.message"; data: { /** @@ -1207,6 +1199,14 @@ export type SessionEvent = displayName?: string; } )[]; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit + */ + nativeDocumentPathFallbackPaths?: string[]; /** * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) */ @@ -1235,6 +1235,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "pending_messages.modified"; /** * Empty payload; the event signals that the pending message queue has changed @@ -1258,6 +1262,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.turn_start"; /** * Turn initialization metadata including identifier and interaction tracking @@ -1287,6 +1295,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.intent"; /** * Agent intent description for current activity or plan @@ -1315,6 +1327,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.reasoning"; /** * Assistant reasoning content for timeline display with complete thinking text @@ -1344,6 +1360,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.reasoning_delta"; /** * Streaming reasoning delta for incremental extended thinking updates @@ -1373,6 +1393,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.streaming_delta"; /** * Streaming response progress with cumulative byte count @@ -1401,6 +1425,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.message"; /** * Assistant response containing text content, optional tool requests, and interaction metadata @@ -1478,6 +1506,7 @@ export type SessionEvent = */ requestId?: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1497,6 +1526,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.message_delta"; /** * Streaming assistant message delta for incremental response updates @@ -1511,6 +1544,7 @@ export type SessionEvent = */ deltaContent: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1533,6 +1567,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.turn_end"; /** * Turn completion metadata including the turn identifier @@ -1558,6 +1596,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "assistant.usage"; /** * LLM API call usage metrics including tokens, costs, quotas, and billing information @@ -1616,6 +1658,7 @@ export type SessionEvent = */ providerCallId?: string; /** + * @deprecated * Parent tool call ID when this usage originates from a sub-agent */ parentToolCallId?: string; @@ -1711,6 +1754,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "abort"; /** * Turn abort information including the reason for termination @@ -1739,6 +1786,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.user_requested"; /** * User-initiated tool invocation request with tool name and arguments @@ -1777,6 +1828,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_start"; /** * Tool execution startup details including MCP server information when applicable @@ -1805,6 +1860,7 @@ export type SessionEvent = */ mcpToolName?: string; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -1824,6 +1880,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_partial_result"; /** * Streaming tool execution output for incremental result display @@ -1853,6 +1913,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_progress"; /** * Tool execution progress notification with status message @@ -1885,6 +1949,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "tool.execution_complete"; /** * Tool execution completion results including success status, detailed output, and error information @@ -2061,6 +2129,7 @@ export type SessionEvent = [k: string]: unknown; }; /** + * @deprecated * Tool call ID of the parent tool invocation when this event originates from a sub-agent */ parentToolCallId?: string; @@ -2083,6 +2152,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "skill.invoked"; /** * Skill invocation details including content, allowed tools, and plugin metadata @@ -2135,6 +2208,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.started"; /** * Sub-agent startup details including parent tool call and agent information @@ -2175,6 +2252,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.completed"; /** * Sub-agent completion details for successful execution @@ -2227,6 +2308,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.failed"; /** * Sub-agent failure details including error message and agent information @@ -2283,6 +2368,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.selected"; /** * Custom agent selection details including name and available tools @@ -2319,6 +2408,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "subagent.deselected"; /** * Empty payload; the event signals that the custom agent was deselected, returning to the default agent @@ -2342,6 +2435,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "hook.start"; /** * Hook invocation start details including type and input data @@ -2380,6 +2477,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "hook.end"; /** * Hook invocation completion details including output, success status, and error information @@ -2435,13 +2536,17 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "system.message"; /** - * System or developer message content with role and optional template metadata + * System/developer instruction content with role and optional template metadata */ data: { /** - * The system or developer prompt text + * The system or developer prompt text sent as model input */ content: string; /** @@ -2486,6 +2591,10 @@ export type SessionEvent = * When true, the event is transient and not persisted to the session event log on disk */ ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "system.notification"; /** * System-generated notification for runtime events like background task completion @@ -2579,6 +2688,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "permission.requested"; /** * Permission request notification requiring client approval with request details @@ -2673,6 +2786,10 @@ export type SessionEvent = * Complete new file contents for newly created files */ newFileContents?: string; + /** + * Whether the UI can offer session-wide approval for file write operations + */ + canOfferSessionApproval: boolean; } | { /** @@ -2844,6 +2961,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "permission.completed"; /** * Permission request completion notification signaling UI dismissal @@ -2884,6 +3005,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user_input.requested"; /** * User input request notification with question and optional predefined choices @@ -2925,6 +3050,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "user_input.completed"; /** * User input request completion with the user's response @@ -2958,6 +3087,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "elicitation.requested"; /** * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) @@ -3023,6 +3156,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "elicitation.completed"; /** * Elicitation request completion with the user's response @@ -3058,6 +3195,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "sampling.requested"; /** * Sampling request from an MCP server; contains the server name and a requestId for correlation @@ -3092,6 +3233,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "sampling.completed"; /** * Sampling request completion notification signaling UI dismissal @@ -3117,6 +3262,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "mcp.oauth_required"; /** * OAuth authentication request for an MCP server @@ -3163,6 +3312,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "mcp.oauth_completed"; /** * MCP OAuth request completion notification @@ -3188,6 +3341,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "external_tool.requested"; /** * External tool invocation request for client-side tool execution @@ -3239,6 +3396,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "external_tool.completed"; /** * External tool completion notification signaling UI dismissal @@ -3264,6 +3425,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.queued"; /** * Queued slash command dispatch request for client execution @@ -3293,6 +3458,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.execute"; /** * Registered command dispatch request routed to the owning client @@ -3330,6 +3499,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "command.completed"; /** * Queued command completion notification signaling UI dismissal @@ -3355,6 +3528,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "commands.changed"; /** * SDK command registration change notification @@ -3383,6 +3560,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "capabilities.changed"; /** * Session capability change notification @@ -3413,6 +3594,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "exit_plan_mode.requested"; /** * Plan approval request with plan content and available user actions @@ -3454,6 +3639,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "exit_plan_mode.completed"; /** * Plan mode exit completion with the user's approval decision and optional feedback @@ -3495,6 +3684,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.tools_updated"; data: { model: string; @@ -3514,6 +3707,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.background_tasks_changed"; data: {}; } @@ -3531,6 +3728,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.skills_loaded"; data: { /** @@ -3578,6 +3779,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.custom_agents_updated"; data: { /** @@ -3641,6 +3846,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mcp_servers_loaded"; data: { /** @@ -3680,6 +3889,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.mcp_server_status_changed"; data: { /** @@ -3706,6 +3919,10 @@ export type SessionEvent = */ parentId: string | null; ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; type: "session.extensions_loaded"; data: { /** @@ -3732,6 +3949,39 @@ export type SessionEvent = }; }; +/** + * Working directory and git context at session start + */ +export interface WorkingDirectoryContext { + /** + * Current working directory path + */ + cwd: string; + /** + * Root directory of the git repository, resolved via git rev-parse + */ + gitRoot?: string; + /** + * Repository identifier derived from the git remote URL ("owner/name" for GitHub, "org/project/repo" for Azure DevOps) + */ + repository?: string; + /** + * Hosting platform type of the repository (github or ado) + */ + hostType?: "github" | "ado"; + /** + * Current git branch name + */ + branch?: string; + /** + * Head commit of current git branch at session start time + */ + headCommit?: string; + /** + * Base commit of current git branch at session start time + */ + baseCommit?: string; +} export interface EmbeddedTextResourceContents { /** * URI identifying the resource diff --git a/nodejs/test/e2e/session_fs.test.ts b/nodejs/test/e2e/session_fs.test.ts index fa74e10a4..5d6ad26da 100644 --- a/nodejs/test/e2e/session_fs.test.ts +++ b/nodejs/test/e2e/session_fs.test.ts @@ -239,17 +239,17 @@ function createTestSessionFsHandler( writeFile: async ({ path, content }) => { try { await provider.writeFile(sp(session.sessionId, path), content); - return {}; + return undefined; } catch (err) { - return { error: mapError(err) }; + return mapError(err); } }, appendFile: async ({ path, content }) => { try { await provider.appendFile(sp(session.sessionId, path), content); - return {}; + return undefined; } catch (err) { - return { error: mapError(err) }; + return mapError(err); } }, exists: async ({ path }) => { @@ -275,9 +275,9 @@ function createTestSessionFsHandler( recursive: recursive ?? false, mode, }); - return {}; + return undefined; } catch (err) { - return { error: mapError(err) }; + return mapError(err); } }, readdir: async ({ path }) => { @@ -308,17 +308,17 @@ function createTestSessionFsHandler( rm: async ({ path }) => { try { await provider.unlink(sp(session.sessionId, path)); - return {}; + return undefined; } catch (err) { - return { error: mapError(err) }; + return mapError(err); } }, rename: async ({ src, dest }) => { try { await provider.rename(sp(session.sessionId, src), sp(session.sessionId, dest)); - return {}; + return undefined; } catch (err) { - return { error: mapError(err) }; + return mapError(err); } }, }; diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 1aa658823..6bfe8258d 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -72,34 +72,6 @@ def to_float(x: Any) -> float: def from_datetime(x: Any) -> datetime: return dateutil.parser.parse(x) -@dataclass -class PurpleModelCapabilitiesLimitsVision: - """Vision-specific limits""" - - max_prompt_image_size: int - """Maximum image size in bytes""" - - max_prompt_images: int - """Maximum number of images per prompt""" - - supported_media_types: list[str] - """MIME types the model accepts""" - - @staticmethod - def from_dict(obj: Any) -> 'PurpleModelCapabilitiesLimitsVision': - assert isinstance(obj, dict) - max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) - max_prompt_images = from_int(obj.get("max_prompt_images")) - supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return PurpleModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) - - def to_dict(self) -> dict: - result: dict = {} - result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) - result["max_prompt_images"] = from_int(self.max_prompt_images) - result["supported_media_types"] = from_list(from_str, self.supported_media_types) - return result - @dataclass class ModelCapabilitiesSupports: """Feature flags indicating what the model supports""" @@ -184,54 +156,6 @@ class DiscoveredMCPServerType(Enum): SSE = "sse" STDIO = "stdio" -@dataclass -class SkillElement: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled (based on global config)""" - - name: str - """Unique identifier for the skill""" - - source: str - """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" - - path: str | None = None - """Absolute path to the skill file""" - - project_path: str | None = None - """The project path this skill belongs to (only for project/inherited skills)""" - - @staticmethod - def from_dict(obj: Any) -> 'SkillElement': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return SkillElement(description, enabled, name, source, user_invocable, path, project_path) - - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) - return result - @dataclass class ServerSkill: description: str @@ -458,24 +382,6 @@ def to_dict(self) -> dict: class UIElicitationArrayEnumFieldType(Enum): ARRAY = "array" -@dataclass -class PurpleUIElicitationArrayAnyOfFieldItemsAnyOf: - const: str - title: str - - @staticmethod - def from_dict(obj: Any) -> 'PurpleUIElicitationArrayAnyOfFieldItemsAnyOf': - assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return PurpleUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) - - def to_dict(self) -> dict: - result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) - return result - class UIElicitationResponseAction(Enum): """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" @@ -525,6 +431,12 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) return result +class SessionFSErrorCode(Enum): + """Error classification""" + + ENOENT = "ENOENT" + UNKNOWN = "UNKNOWN" + @dataclass class PingResult: message: str @@ -586,59 +498,6 @@ def to_dict(self) -> dict: result["multiplier"] = to_float(self.multiplier) return result -@dataclass -class FluffyModelCapabilitiesLimitsVision: - """Vision-specific limits""" - - max_prompt_image_size: int - """Maximum image size in bytes""" - - max_prompt_images: int - """Maximum number of images per prompt""" - - supported_media_types: list[str] - """MIME types the model accepts""" - - @staticmethod - def from_dict(obj: Any) -> 'FluffyModelCapabilitiesLimitsVision': - assert isinstance(obj, dict) - max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) - max_prompt_images = from_int(obj.get("max_prompt_images")) - supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return FluffyModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) - - def to_dict(self) -> dict: - result: dict = {} - result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) - result["max_prompt_images"] = from_int(self.max_prompt_images) - result["supported_media_types"] = from_list(from_str, self.supported_media_types) - return result - -@dataclass -class CapabilitiesSupports: - """Feature flags indicating what the model supports""" - - reasoning_effort: bool | None = None - """Whether this model supports reasoning effort configuration""" - - vision: bool | None = None - """Whether this model supports vision/image input""" - - @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesSupports': - assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return CapabilitiesSupports(reasoning_effort, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) - return result - @dataclass class ModelPolicy: """Policy state (if applicable)""" @@ -916,35 +775,6 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result -@dataclass -class FluffyModelCapabilitiesOverrideLimitsVision: - max_prompt_image_size: int | None = None - """Maximum image size in bytes""" - - max_prompt_images: int | None = None - """Maximum number of images per prompt""" - - supported_media_types: list[str] | None = None - """MIME types the model accepts""" - - @staticmethod - def from_dict(obj: Any) -> 'FluffyModelCapabilitiesOverrideLimitsVision': - assert isinstance(obj, dict) - max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) - max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) - supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) - return FluffyModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_prompt_image_size is not None: - result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) - if self.max_prompt_images is not None: - result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) - if self.supported_media_types is not None: - result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) - return result - class SessionMode(Enum): """The agent mode. Valid values: "interactive", "plan", "autopilot".""" @@ -1156,32 +986,6 @@ def to_dict(self) -> dict: result["prompt"] = from_union([from_str, from_none], self.prompt) return result -@dataclass -class AgentListAgent: - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" - - name: str - """Unique identifier of the custom agent""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentListAgent': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentListAgent(description, display_name, name) - - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) - return result - @dataclass class AgentSelectResultAgent: """The newly selected custom agent""" @@ -1227,32 +1031,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result -@dataclass -class AgentReloadResultAgent: - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" - - name: str - """Unique identifier of the custom agent""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResultAgent': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentReloadResultAgent(description, display_name, name) - - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) - return result - @dataclass class Skill: description: str @@ -1485,42 +1263,6 @@ class UIElicitationSchemaPropertyStringFormat(Enum): EMAIL = "email" URI = "uri" -@dataclass -class FluffyUIElicitationArrayAnyOfFieldItemsAnyOf: - const: str - title: str - - @staticmethod - def from_dict(obj: Any) -> 'FluffyUIElicitationArrayAnyOfFieldItemsAnyOf': - assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return FluffyUIElicitationArrayAnyOfFieldItemsAnyOf(const, title) - - def to_dict(self) -> dict: - result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) - return result - -@dataclass -class UIElicitationSchemaPropertyOneOf: - const: str - title: str - - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyOneOf': - assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationSchemaPropertyOneOf(const, title) - - def to_dict(self) -> dict: - result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) - return result - class UIElicitationSchemaPropertyNumberType(Enum): ARRAY = "array" BOOLEAN = "boolean" @@ -1792,22 +1534,6 @@ def to_dict(self) -> dict: result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) return result -@dataclass -class SessionFSReadFileResult: - content: str - """File content as UTF-8 string""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileResult': - assert isinstance(obj, dict) - content = from_str(obj.get("content")) - return SessionFSReadFileResult(content) - - def to_dict(self) -> dict: - result: dict = {} - result["content"] = from_str(self.content) - return result - @dataclass class SessionFSReadFileRequest: path: str @@ -1930,42 +1656,6 @@ def to_dict(self) -> dict: result["sessionId"] = from_str(self.session_id) return result -@dataclass -class SessionFSStatResult: - birthtime: datetime - """ISO 8601 timestamp of creation""" - - is_directory: bool - """Whether the path is a directory""" - - is_file: bool - """Whether the path is a file""" - - mtime: datetime - """ISO 8601 timestamp of last modification""" - - size: int - """File size in bytes""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatResult': - assert isinstance(obj, dict) - birthtime = from_datetime(obj.get("birthtime")) - is_directory = from_bool(obj.get("isDirectory")) - is_file = from_bool(obj.get("isFile")) - mtime = from_datetime(obj.get("mtime")) - size = from_int(obj.get("size")) - return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size) - - def to_dict(self) -> dict: - result: dict = {} - result["birthtime"] = self.birthtime.isoformat() - result["isDirectory"] = from_bool(self.is_directory) - result["isFile"] = from_bool(self.is_file) - result["mtime"] = self.mtime.isoformat() - result["size"] = from_int(self.size) - return result - @dataclass class SessionFSStatRequest: path: str @@ -2020,22 +1710,6 @@ def to_dict(self) -> dict: result["recursive"] = from_union([from_bool, from_none], self.recursive) return result -@dataclass -class SessionFSReaddirResult: - entries: list[str] - """Entry names in the directory""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirResult': - assert isinstance(obj, dict) - entries = from_list(from_str, obj.get("entries")) - return SessionFSReaddirResult(entries) - - def to_dict(self) -> dict: - result: dict = {} - result["entries"] = from_list(from_str, self.entries) - return result - @dataclass class SessionFSReaddirRequest: path: str @@ -2156,7 +1830,7 @@ class ModelCapabilitiesLimits: max_prompt_tokens: int | None = None """Maximum number of prompt/input tokens""" - vision: PurpleModelCapabilitiesLimitsVision | None = None + vision: ModelCapabilitiesLimitsVision | None = None """Vision-specific limits""" @staticmethod @@ -2165,7 +1839,7 @@ def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([PurpleModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: @@ -2177,227 +1851,48 @@ def to_dict(self) -> dict: if self.max_prompt_tokens is not None: result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesLimitsVision, x), from_none], self.vision) - return result - -@dataclass -class MCPServerConfig: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" - - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfig': - assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) - - def to_dict(self) -> dict: - result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) - return result - -@dataclass -class MCPServerConfigValue: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" - - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" - - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" - - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigValue': - assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfigValue(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) - - def to_dict(self) -> dict: - result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result @dataclass -class MCPConfigAddRequestMCPServerConfig: - """MCP server configuration (local/stdio or remote/http)""" +class CapabilitiesLimits: + """Token limits for prompts, outputs, and context window""" - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + max_output_tokens: int | None = None + """Maximum number of output/completion tokens""" - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" + max_prompt_tokens: int | None = None + """Maximum number of prompt/input tokens""" - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None + vision: ModelCapabilitiesLimitsVision | None = None + """Vision-specific limits""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigAddRequestMCPServerConfig': + def from_dict(obj: Any) -> 'CapabilitiesLimits': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigAddRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + return CapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result @dataclass -class MCPConfigUpdateRequestMCPServerConfig: +class MCPServerConfig: """MCP server configuration (local/stdio or remote/http)""" args: list[str] | None = None @@ -2421,7 +1916,7 @@ class MCPConfigUpdateRequestMCPServerConfig: url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'MCPConfigUpdateRequestMCPServerConfig': + def from_dict(obj: Any) -> 'MCPServerConfig': assert isinstance(obj, dict) args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) command = from_union([from_str, from_none], obj.get("command")) @@ -2436,7 +1931,7 @@ def from_dict(obj: Any) -> 'MCPConfigUpdateRequestMCPServerConfig': oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) url = from_union([from_str, from_none], obj.get("url")) - return MCPConfigUpdateRequestMCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} @@ -2501,55 +1996,55 @@ def to_dict(self) -> dict: return result @dataclass -class ServerElement: - enabled: bool - """Whether the server is enabled (not in the disabled list)""" - - name: str - """Server name (config key)""" - - source: MCPServerSource - """Configuration source""" - - type: DiscoveredMCPServerType | None = None - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" +class ServerSkillList: + skills: list[ServerSkill] + """All discovered skills across all sources""" @staticmethod - def from_dict(obj: Any) -> 'ServerElement': + def from_dict(obj: Any) -> 'ServerSkillList': assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = MCPServerSource(obj.get("source")) - type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) - return ServerElement(enabled, name, source, type) + skills = from_list(ServerSkill.from_dict, obj.get("skills")) + return ServerSkillList(skills) def to_dict(self) -> dict: result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = to_enum(MCPServerSource, self.source) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(DiscoveredMCPServerType, x), from_none], self.type) + result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) return result @dataclass -class ServerSkillList: - skills: list[SkillElement] - """All discovered skills across all sources""" +class ModelCapabilitiesOverrideLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None + vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None @staticmethod - def from_dict(obj: Any) -> 'ServerSkillList': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': assert isinstance(obj, dict) - skills = from_list(SkillElement.from_dict, obj.get("skills")) - return ServerSkillList(skills) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - result["skills"] = from_list(lambda x: to_class(SkillElement, x), self.skills) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) return result @dataclass -class ModelCapabilitiesOverrideLimits: +class ModelCapabilitiesLimitsClass: """Token limits for prompts, outputs, and context window""" max_context_window_tokens: int | None = None @@ -2560,13 +2055,13 @@ class ModelCapabilitiesOverrideLimits: vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsClass': assert isinstance(obj, dict) max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + return ModelCapabilitiesLimitsClass(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} @@ -2580,6 +2075,58 @@ def to_dict(self) -> dict: result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) return result +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentList: + agents: list[AgentInfo] + """Available custom agents""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentList': + assert isinstance(obj, dict) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentList(agents) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentGetCurrentResult: + agent: AgentInfo | None = None + """Currently selected custom agent, or null if using the default agent""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentGetCurrentResult': + assert isinstance(obj, dict) + agent = from_union([AgentInfo.from_dict, from_none], obj.get("agent")) + return AgentGetCurrentResult(agent) + + def to_dict(self) -> dict: + result: dict = {} + if self.agent is not None: + result["agent"] = from_union([lambda x: to_class(AgentInfo, x), from_none], self.agent) + return result + +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentReloadResult: + agents: list[AgentInfo] + """Reloaded custom agents""" + + @staticmethod + def from_dict(obj: Any) -> 'AgentReloadResult': + assert isinstance(obj, dict) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentReloadResult(agents) + + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) + return result + @dataclass class MCPServer: name: str @@ -2697,17 +2244,41 @@ def to_dict(self) -> dict: @dataclass class UIElicitationArrayAnyOfFieldItems: - any_of: list[PurpleUIElicitationArrayAnyOfFieldItemsAnyOf] + any_of: list[UIElicitationStringOneOfFieldOneOf] @staticmethod def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': assert isinstance(obj, dict) - any_of = from_list(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, obj.get("anyOf")) + any_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("anyOf")) return UIElicitationArrayAnyOfFieldItems(any_of) def to_dict(self) -> dict: result: dict = {} - result["anyOf"] = from_list(lambda x: to_class(PurpleUIElicitationArrayAnyOfFieldItemsAnyOf, x), self.any_of) + result["anyOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.any_of) + return result + +@dataclass +class UIElicitationArrayFieldItems: + enum: list[str] | None = None + type: UIElicitationStringEnumFieldType | None = None + any_of: list[UIElicitationStringOneOfFieldOneOf] | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': + assert isinstance(obj, dict) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + type = from_union([UIElicitationStringEnumFieldType, from_none], obj.get("type")) + any_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("anyOf")) + return UIElicitationArrayFieldItems(enum, type, any_of) + + def to_dict(self) -> dict: + result: dict = {} + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(UIElicitationStringEnumFieldType, x), from_none], self.type) + if self.any_of is not None: + result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.any_of) return result @dataclass @@ -2793,40 +2364,27 @@ def to_dict(self) -> dict: return result @dataclass -class CapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" +class SessionFSError: + """Describes a filesystem error.""" - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" - - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" + code: SessionFSErrorCode + """Error classification""" - vision: FluffyModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" + message: str | None = None + """Free-form detail about the error, for logging/diagnostics""" @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesLimits': + def from_dict(obj: Any) -> 'SessionFSError': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([FluffyModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return CapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + code = SessionFSErrorCode(obj.get("code")) + message = from_union([from_str, from_none], obj.get("message")) + return SessionFSError(code, message) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesLimitsVision, x), from_none], self.vision) + result["code"] = to_enum(SessionFSErrorCode, self.code) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result @dataclass @@ -2915,38 +2473,6 @@ def to_dict(self) -> dict: result["sessionStatePath"] = from_str(self.session_state_path) return result -@dataclass -class ModelCapabilitiesLimitsClass: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: FluffyModelCapabilitiesOverrideLimitsVision | None = None - - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsClass': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([FluffyModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimitsClass(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) - - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(FluffyModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) - return result - @dataclass class ModeSetRequest: mode: SessionMode @@ -2976,7 +2502,6 @@ class Workspace: mc_session_id: str | None = None mc_task_id: str | None = None name: str | None = None - pr_create_sync_dismissed: bool | None = None repository: str | None = None session_sync_level: SessionSyncLevel | None = None summary: str | None = None @@ -2997,13 +2522,12 @@ def from_dict(obj: Any) -> 'Workspace': mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) name = from_union([from_str, from_none], obj.get("name")) - pr_create_sync_dismissed = from_union([from_bool, from_none], obj.get("pr_create_sync_dismissed")) repository = from_union([from_str, from_none], obj.get("repository")) session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) summary = from_union([from_str, from_none], obj.get("summary")) summary_count = from_union([from_int, from_none], obj.get("summary_count")) updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, pr_create_sync_dismissed, repository, session_sync_level, summary, summary_count, updated_at) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, repository, session_sync_level, summary, summary_count, updated_at) def to_dict(self) -> dict: result: dict = {} @@ -3028,8 +2552,6 @@ def to_dict(self) -> dict: result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) - if self.pr_create_sync_dismissed is not None: - result["pr_create_sync_dismissed"] = from_union([from_bool, from_none], self.pr_create_sync_dismissed) if self.repository is not None: result["repository"] = from_union([from_str, from_none], self.repository) if self.session_sync_level is not None: @@ -3095,23 +2617,6 @@ def to_dict(self) -> dict: result["description"] = from_union([from_str, from_none], self.description) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentList: - agents: list[AgentListAgent] - """Available custom agents""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentList': - assert isinstance(obj, dict) - agents = from_list(AgentListAgent.from_dict, obj.get("agents")) - return AgentList(agents) - - def to_dict(self) -> dict: - result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentListAgent, x), self.agents) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class AgentSelectResult: @@ -3129,41 +2634,6 @@ def to_dict(self) -> dict: result["agent"] = to_class(AgentSelectResultAgent, self.agent) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentGetCurrentResult: - agent: AgentReloadResultAgent | None = None - """Currently selected custom agent, or null if using the default agent""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentGetCurrentResult': - assert isinstance(obj, dict) - agent = from_union([AgentReloadResultAgent.from_dict, from_none], obj.get("agent")) - return AgentGetCurrentResult(agent) - - def to_dict(self) -> dict: - result: dict = {} - if self.agent is not None: - result["agent"] = from_union([lambda x: to_class(AgentReloadResultAgent, x), from_none], self.agent) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentReloadResult: - agents: list[AgentReloadResultAgent] - """Reloaded custom agents""" - - @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResult': - assert isinstance(obj, dict) - agents = from_list(AgentReloadResultAgent.from_dict, obj.get("agents")) - return AgentReloadResult(agents) - - def to_dict(self) -> dict: - result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentReloadResultAgent, x), self.agents) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class SkillList: @@ -3224,39 +2694,15 @@ def from_dict(obj: Any) -> 'Extension': status = ExtensionStatus(obj.get("status")) pid = from_union([from_int, from_none], obj.get("pid")) return Extension(id, name, source, status, pid) - - def to_dict(self) -> dict: - result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionSource, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) - if self.pid is not None: - result["pid"] = from_union([from_int, from_none], self.pid) - return result - -@dataclass -class UIElicitationArrayFieldItems: - enum: list[str] | None = None - type: UIElicitationStringEnumFieldType | None = None - any_of: list[FluffyUIElicitationArrayAnyOfFieldItemsAnyOf] | None = None - - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': - assert isinstance(obj, dict) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - type = from_union([UIElicitationStringEnumFieldType, from_none], obj.get("type")) - any_of = from_union([lambda x: from_list(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) - return UIElicitationArrayFieldItems(enum, type, any_of) - - def to_dict(self) -> dict: - result: dict = {} - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(UIElicitationStringEnumFieldType, x), from_none], self.type) - if self.any_of is not None: - result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(FluffyUIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionSource, self.source) + result["status"] = to_enum(ExtensionStatus, self.status) + if self.pid is not None: + result["pid"] = from_union([from_int, from_none], self.pid) return result @dataclass @@ -3393,23 +2839,23 @@ def to_dict(self) -> dict: @dataclass class MCPConfigList: - servers: dict[str, MCPServerConfigValue] + servers: dict[str, MCPServerConfig] """All MCP servers from user config, keyed by name""" @staticmethod def from_dict(obj: Any) -> 'MCPConfigList': assert isinstance(obj, dict) - servers = from_dict(MCPServerConfigValue.from_dict, obj.get("servers")) + servers = from_dict(MCPServerConfig.from_dict, obj.get("servers")) return MCPConfigList(servers) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_dict(lambda x: to_class(MCPServerConfigValue, x), self.servers) + result["servers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.servers) return result @dataclass class MCPConfigAddRequest: - config: MCPConfigAddRequestMCPServerConfig + config: MCPServerConfig """MCP server configuration (local/stdio or remote/http)""" name: str @@ -3418,19 +2864,19 @@ class MCPConfigAddRequest: @staticmethod def from_dict(obj: Any) -> 'MCPConfigAddRequest': assert isinstance(obj, dict) - config = MCPConfigAddRequestMCPServerConfig.from_dict(obj.get("config")) + config = MCPServerConfig.from_dict(obj.get("config")) name = from_str(obj.get("name")) return MCPConfigAddRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigAddRequestMCPServerConfig, self.config) + result["config"] = to_class(MCPServerConfig, self.config) result["name"] = from_str(self.name) return result @dataclass class MCPConfigUpdateRequest: - config: MCPConfigUpdateRequestMCPServerConfig + config: MCPServerConfig """MCP server configuration (local/stdio or remote/http)""" name: str @@ -3439,30 +2885,30 @@ class MCPConfigUpdateRequest: @staticmethod def from_dict(obj: Any) -> 'MCPConfigUpdateRequest': assert isinstance(obj, dict) - config = MCPConfigUpdateRequestMCPServerConfig.from_dict(obj.get("config")) + config = MCPServerConfig.from_dict(obj.get("config")) name = from_str(obj.get("name")) return MCPConfigUpdateRequest(config, name) def to_dict(self) -> dict: result: dict = {} - result["config"] = to_class(MCPConfigUpdateRequestMCPServerConfig, self.config) + result["config"] = to_class(MCPServerConfig, self.config) result["name"] = from_str(self.name) return result @dataclass class MCPDiscoverResult: - servers: list[ServerElement] + servers: list[DiscoveredMCPServer] """MCP servers discovered from all sources""" @staticmethod def from_dict(obj: Any) -> 'MCPDiscoverResult': assert isinstance(obj, dict) - servers = from_list(ServerElement.from_dict, obj.get("servers")) + servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) return MCPDiscoverResult(servers) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(ServerElement, x), self.servers) + result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) return result @dataclass @@ -3490,6 +2936,31 @@ def to_dict(self) -> dict: result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) return result +@dataclass +class ModelCapabilitiesClass: + """Override individual model capabilities resolved by the runtime""" + + limits: ModelCapabilitiesLimitsClass | None = None + """Token limits for prompts, outputs, and context window""" + + supports: ModelCapabilitiesOverrideSupports | None = None + """Feature flags indicating what the model supports""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesClass': + assert isinstance(obj, dict) + limits = from_union([ModelCapabilitiesLimitsClass.from_dict, from_none], obj.get("limits")) + supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) + return ModelCapabilitiesClass(limits, supports) + + def to_dict(self) -> dict: + result: dict = {} + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsClass, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + return result + @dataclass class MCPServerList: servers: list[MCPServer] @@ -3582,6 +3053,77 @@ def to_dict(self) -> dict: result["title"] = from_union([from_str, from_none], self.title) return result +@dataclass +class UIElicitationSchemaProperty: + type: UIElicitationSchemaPropertyNumberType + default: float | bool | list[str] | str | None = None + description: str | None = None + enum: list[str] | None = None + enum_names: list[str] | None = None + title: str | None = None + one_of: list[UIElicitationStringOneOfFieldOneOf] | None = None + items: UIElicitationArrayFieldItems | None = None + max_items: float | None = None + min_items: float | None = None + format: UIElicitationSchemaPropertyStringFormat | None = None + max_length: float | None = None + min_length: float | None = None + maximum: float | None = None + minimum: float | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': + assert isinstance(obj, dict) + type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) + title = from_union([from_str, from_none], obj.get("title")) + one_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("oneOf")) + items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) + max_items = from_union([from_float, from_none], obj.get("maxItems")) + min_items = from_union([from_float, from_none], obj.get("minItems")) + format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) + max_length = from_union([from_float, from_none], obj.get("maxLength")) + min_length = from_union([from_float, from_none], obj.get("minLength")) + maximum = from_union([from_float, from_none], obj.get("maximum")) + minimum = from_union([from_float, from_none], obj.get("minimum")) + return UIElicitationSchemaProperty(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + if self.default is not None: + result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.enum_names is not None: + result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + if self.one_of is not None: + result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.one_of) + if self.items is not None: + result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) + if self.max_items is not None: + result["maxItems"] = from_union([to_float, from_none], self.max_items) + if self.min_items is not None: + result["minItems"] = from_union([to_float, from_none], self.min_items) + if self.format is not None: + result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) + if self.max_length is not None: + result["maxLength"] = from_union([to_float, from_none], self.max_length) + if self.min_length is not None: + result["minLength"] = from_union([to_float, from_none], self.min_length) + if self.maximum is not None: + result["maximum"] = from_union([to_float, from_none], self.maximum) + if self.minimum is not None: + result["minimum"] = from_union([to_float, from_none], self.minimum) + return result + @dataclass class UIHandlePendingElicitationRequest: request_id: str @@ -3624,28 +3166,89 @@ def to_dict(self) -> dict: return result @dataclass -class ModelCapabilitiesClass: - """Override individual model capabilities resolved by the runtime""" +class SessionFSReadFileResult: + content: str + """File content as UTF-8 string""" - limits: ModelCapabilitiesLimitsClass | None = None - """Token limits for prompts, outputs, and context window""" + error: SessionFSError | None = None + """Describes a filesystem error.""" - supports: ModelCapabilitiesOverrideSupports | None = None - """Feature flags indicating what the model supports""" + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReadFileResult': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReadFileResult(content, error) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + return result + +@dataclass +class SessionFSStatResult: + birthtime: datetime + """ISO 8601 timestamp of creation""" + + is_directory: bool + """Whether the path is a directory""" + + is_file: bool + """Whether the path is a file""" + + mtime: datetime + """ISO 8601 timestamp of last modification""" + + size: int + """File size in bytes""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesClass': + def from_dict(obj: Any) -> 'SessionFSStatResult': assert isinstance(obj, dict) - limits = from_union([ModelCapabilitiesLimitsClass.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) - return ModelCapabilitiesClass(limits, supports) + birthtime = from_datetime(obj.get("birthtime")) + is_directory = from_bool(obj.get("isDirectory")) + is_file = from_bool(obj.get("isFile")) + mtime = from_datetime(obj.get("mtime")) + size = from_int(obj.get("size")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size, error) def to_dict(self) -> dict: result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsClass, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + result["birthtime"] = self.birthtime.isoformat() + result["isDirectory"] = from_bool(self.is_directory) + result["isFile"] = from_bool(self.is_file) + result["mtime"] = self.mtime.isoformat() + result["size"] = from_int(self.size) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + return result + +@dataclass +class SessionFSReaddirResult: + entries: list[str] + """Entry names in the directory""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirResult': + assert isinstance(obj, dict) + entries = from_list(from_str, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirResult(entries, error) + + def to_dict(self) -> dict: + result: dict = {} + result["entries"] = from_list(from_str, self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass @@ -3697,77 +3300,6 @@ def to_dict(self) -> dict: result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result -@dataclass -class UIElicitationSchemaProperty: - type: UIElicitationSchemaPropertyNumberType - default: float | bool | list[str] | str | None = None - description: str | None = None - enum: list[str] | None = None - enum_names: list[str] | None = None - title: str | None = None - one_of: list[UIElicitationSchemaPropertyOneOf] | None = None - items: UIElicitationArrayFieldItems | None = None - max_items: float | None = None - min_items: float | None = None - format: UIElicitationSchemaPropertyStringFormat | None = None - max_length: float | None = None - min_length: float | None = None - maximum: float | None = None - minimum: float | None = None - - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': - assert isinstance(obj, dict) - type = UIElicitationSchemaPropertyNumberType(obj.get("type")) - default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) - title = from_union([from_str, from_none], obj.get("title")) - one_of = from_union([lambda x: from_list(UIElicitationSchemaPropertyOneOf.from_dict, x), from_none], obj.get("oneOf")) - items = from_union([UIElicitationArrayFieldItems.from_dict, from_none], obj.get("items")) - max_items = from_union([from_float, from_none], obj.get("maxItems")) - min_items = from_union([from_float, from_none], obj.get("minItems")) - format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) - max_length = from_union([from_float, from_none], obj.get("maxLength")) - min_length = from_union([from_float, from_none], obj.get("minLength")) - maximum = from_union([from_float, from_none], obj.get("maximum")) - minimum = from_union([from_float, from_none], obj.get("minimum")) - return UIElicitationSchemaProperty(type, default, description, enum, enum_names, title, one_of, items, max_items, min_items, format, max_length, min_length, maximum, minimum) - - def to_dict(self) -> dict: - result: dict = {} - result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) - if self.default is not None: - result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.enum_names is not None: - result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) - if self.one_of is not None: - result["oneOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationSchemaPropertyOneOf, x), x), from_none], self.one_of) - if self.items is not None: - result["items"] = from_union([lambda x: to_class(UIElicitationArrayFieldItems, x), from_none], self.items) - if self.max_items is not None: - result["maxItems"] = from_union([to_float, from_none], self.max_items) - if self.min_items is not None: - result["minItems"] = from_union([to_float, from_none], self.min_items) - if self.format is not None: - result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) - if self.max_length is not None: - result["maxLength"] = from_union([to_float, from_none], self.max_length) - if self.min_length is not None: - result["minLength"] = from_union([to_float, from_none], self.min_length) - if self.maximum is not None: - result["maximum"] = from_union([to_float, from_none], self.maximum) - if self.minimum is not None: - result["minimum"] = from_union([to_float, from_none], self.minimum) - return result - # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class UsageGetMetricsResult: @@ -3832,15 +3364,21 @@ class SessionFSReaddirWithTypesResult: entries: list[SessionFSReaddirWithTypesEntry] """Directory entries with type information""" + error: SessionFSError | None = None + """Describes a filesystem error.""" + @staticmethod def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': assert isinstance(obj, dict) entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) - return SessionFSReaddirWithTypesResult(entries) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirWithTypesResult(entries, error) def to_dict(self) -> dict: result: dict = {} result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass @@ -3925,14 +3463,14 @@ class CapabilitiesClass: limits: CapabilitiesLimits | None = None """Token limits for prompts, outputs, and context window""" - supports: CapabilitiesSupports | None = None + supports: ModelCapabilitiesSupports | None = None """Feature flags indicating what the model supports""" @staticmethod def from_dict(obj: Any) -> 'CapabilitiesClass': assert isinstance(obj, dict) limits = from_union([CapabilitiesLimits.from_dict, from_none], obj.get("limits")) - supports = from_union([CapabilitiesSupports.from_dict, from_none], obj.get("supports")) + supports = from_union([ModelCapabilitiesSupports.from_dict, from_none], obj.get("supports")) return CapabilitiesClass(limits, supports) def to_dict(self) -> dict: @@ -3940,7 +3478,7 @@ def to_dict(self) -> dict: if self.limits is not None: result["limits"] = from_union([lambda x: to_class(CapabilitiesLimits, x), from_none], self.limits) if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(CapabilitiesSupports, x), from_none], self.supports) + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) return result @dataclass @@ -4205,6 +3743,12 @@ def session_log_level_from_dict(s: Any) -> SessionLogLevel: def session_log_level_to_dict(x: SessionLogLevel) -> Any: return to_enum(SessionLogLevel, x) +def session_fs_error_from_dict(s: Any) -> SessionFSError: + return SessionFSError.from_dict(s) + +def session_fs_error_to_dict(x: SessionFSError) -> Any: + return to_class(SessionFSError, x) + def ping_result_from_dict(s: Any) -> PingResult: return PingResult.from_dict(s) @@ -5129,23 +4673,23 @@ async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogR class SessionFsHandler(Protocol): async def read_file(self, params: SessionFSReadFileRequest) -> SessionFSReadFileResult: pass - async def write_file(self, params: SessionFSWriteFileRequest) -> None: + async def write_file(self, params: SessionFSWriteFileRequest) -> SessionFSError: pass - async def append_file(self, params: SessionFSAppendFileRequest) -> None: + async def append_file(self, params: SessionFSAppendFileRequest) -> SessionFSError: pass async def exists(self, params: SessionFSExistsRequest) -> SessionFSExistsResult: pass async def stat(self, params: SessionFSStatRequest) -> SessionFSStatResult: pass - async def mkdir(self, params: SessionFSMkdirRequest) -> None: + async def mkdir(self, params: SessionFSMkdirRequest) -> SessionFSError: pass async def readdir(self, params: SessionFSReaddirRequest) -> SessionFSReaddirResult: pass async def readdir_with_types(self, params: SessionFSReaddirWithTypesRequest) -> SessionFSReaddirWithTypesResult: pass - async def rm(self, params: SessionFSRmRequest) -> None: + async def rm(self, params: SessionFSRmRequest) -> SessionFSError: pass - async def rename(self, params: SessionFSRenameRequest) -> None: + async def rename(self, params: SessionFSRenameRequest) -> SessionFSError: pass @dataclass @@ -5168,15 +4712,15 @@ async def handle_session_fs_write_file(params: dict) -> dict | None: request = SessionFSWriteFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.write_file(request) - return None + result = await handler.write_file(request) + return result.to_dict() client.set_request_handler("sessionFs.writeFile", handle_session_fs_write_file) async def handle_session_fs_append_file(params: dict) -> dict | None: request = SessionFSAppendFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.append_file(request) - return None + result = await handler.append_file(request) + return result.to_dict() client.set_request_handler("sessionFs.appendFile", handle_session_fs_append_file) async def handle_session_fs_exists(params: dict) -> dict | None: request = SessionFSExistsRequest.from_dict(params) @@ -5196,8 +4740,8 @@ async def handle_session_fs_mkdir(params: dict) -> dict | None: request = SessionFSMkdirRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.mkdir(request) - return None + result = await handler.mkdir(request) + return result.to_dict() client.set_request_handler("sessionFs.mkdir", handle_session_fs_mkdir) async def handle_session_fs_readdir(params: dict) -> dict | None: request = SessionFSReaddirRequest.from_dict(params) @@ -5217,13 +4761,13 @@ async def handle_session_fs_rm(params: dict) -> dict | None: request = SessionFSRmRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.rm(request) - return None + result = await handler.rm(request) + return result.to_dict() client.set_request_handler("sessionFs.rm", handle_session_fs_rm) async def handle_session_fs_rename(params: dict) -> dict | None: request = SessionFSRenameRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") - await handler.rename(request) - return None + result = await handler.rename(request) + return result.to_dict() client.set_request_handler("sessionFs.rename", handle_session_fs_rename) diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 8f9d40321..102cbf7a6 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -110,7 +110,7 @@ function postProcessEnumConstants(code: string): string { return code; } -function collapsePlaceholderGoStructs(code: string): string { +function collapsePlaceholderGoStructs(code: string, knownDefinitionNames?: Set): string { const structBlockRe = /((?:\/\/.*\r?\n)*)type\s+(\w+)\s+struct\s*\{[\s\S]*?^\}/gm; const matches = [...code.matchAll(structBlockRe)].map((match) => ({ fullBlock: match[0], @@ -128,12 +128,14 @@ function collapsePlaceholderGoStructs(code: string): string { for (const group of groups.values()) { if (group.length < 2) continue; - const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name), knownDefinitionNames); if (!canonical) continue; for (const duplicate of group) { if (duplicate.name === canonical) continue; - if (!isPlaceholderTypeName(duplicate.name)) continue; + // Only collapse types that quicktype invented (Class suffix or not + // in the schema's named definitions). Preserve intentionally-named types. + if (!isPlaceholderTypeName(duplicate.name) && knownDefinitionNames?.has(duplicate.name.toLowerCase())) continue; code = code.replace(duplicate.fullBlock, ""); code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); @@ -153,10 +155,16 @@ function normalizeGoStructBlock(block: string, name: string): string { .join("\n"); } -function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { +function chooseCanonicalPlaceholderDuplicate(names: string[], knownDefinitionNames?: Set): string | undefined { + // Prefer the name that matches a schema definition — it's intentionally named. + if (knownDefinitionNames) { + const definedName = names.find((name) => knownDefinitionNames.has(name.toLowerCase())); + if (definedName) return definedName; + } + // Fallback for Class-suffix placeholders: pick the non-placeholder name. const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); if (specificNames.length === 0) return undefined; - return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; + return specificNames[0]; } function isPlaceholderTypeName(name: string): boolean { @@ -1060,7 +1068,8 @@ async function generateRpc(schemaPath?: string): Promise { const quicktypeImports = extractQuicktypeImports(qtCode); qtCode = quicktypeImports.code; qtCode = postProcessEnumConstants(qtCode); - qtCode = collapsePlaceholderGoStructs(qtCode); + const knownDefNames = new Set(Object.keys(allDefinitions).map((n) => n.toLowerCase())); + qtCode = collapsePlaceholderGoStructs(qtCode, knownDefNames); // Strip trailing whitespace from quicktype output (gofmt requirement) qtCode = qtCode.replace(/[ \t]+$/gm, ""); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 175c5175b..87619c09e 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -152,7 +152,7 @@ function unwrapRedundantPythonLambdas(code: string): string { ); } -function collapsePlaceholderPythonDataclasses(code: string): string { +function collapsePlaceholderPythonDataclasses(code: string, knownDefinitionNames?: Set): string { const classBlockRe = /(@dataclass\r?\nclass\s+(\w+):[\s\S]*?)(?=^@dataclass|^class\s+\w+|^def\s+\w+|\Z)/gm; const matches = [...code.matchAll(classBlockRe)].map((match) => ({ fullBlock: match[1], @@ -170,12 +170,14 @@ function collapsePlaceholderPythonDataclasses(code: string): string { for (const group of groups.values()) { if (group.length < 2) continue; - const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name)); + const canonical = chooseCanonicalPlaceholderDuplicate(group.map(({ name }) => name), knownDefinitionNames); if (!canonical) continue; for (const duplicate of group) { if (duplicate.name === canonical) continue; - if (!isPlaceholderTypeName(duplicate.name)) continue; + // Only collapse types that quicktype invented (Class suffix or not + // in the schema's named definitions). Preserve intentionally-named types. + if (!isPlaceholderTypeName(duplicate.name) && knownDefinitionNames?.has(duplicate.name.toLowerCase())) continue; code = code.replace(duplicate.fullBlock, ""); code = code.replace(new RegExp(`\\b${duplicate.name}\\b`, "g"), canonical); @@ -346,16 +348,23 @@ function normalizePythonDataclassBlock(block: string, name: string): string { .join("\n"); } -function chooseCanonicalPlaceholderDuplicate(names: string[]): string | undefined { +function chooseCanonicalPlaceholderDuplicate(names: string[], knownDefinitionNames?: Set): string | undefined { + // Prefer the name that matches a schema definition — it's intentionally named. + if (knownDefinitionNames) { + const definedName = names.find((name) => knownDefinitionNames.has(name.toLowerCase())); + if (definedName) return definedName; + } + // Fallback for Class-suffix placeholders: pick the non-placeholder name. const specificNames = names.filter((name) => !isPlaceholderTypeName(name)); if (specificNames.length === 0) return undefined; - return specificNames.sort((left, right) => right.length - left.length || left.localeCompare(right))[0]; + return specificNames[0]; } function isPlaceholderTypeName(name: string): boolean { return name.endsWith("Class"); } + function toSnakeCase(s: string): string { return s .replace(/([a-z])([A-Z])/g, "$1_$2") @@ -1632,7 +1641,8 @@ async function generateRpc(schemaPath?: string): Promise { typesCode = typesCode.replace(/^(\s*)pass\n\n(\s*@staticmethod)/gm, "$2"); // Modernize to Python 3.11+ syntax typesCode = modernizePython(typesCode); - typesCode = collapsePlaceholderPythonDataclasses(typesCode); + const knownDefNames = new Set(Object.keys(allDefinitions).map((n) => n.toLowerCase())); + typesCode = collapsePlaceholderPythonDataclasses(typesCode, knownDefNames); // Reorder class/enum definitions to resolve forward references. // Quicktype may emit classes before their dependencies are defined. diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index bc144bf75..063857d11 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -251,8 +251,10 @@ export function cloneSchemaForCodegen(value: T): T { } if (value && typeof value === "object") { + const source = value as Record; const result: Record = {}; - for (const [key, child] of Object.entries(value as Record)) { + + for (const [key, child] of Object.entries(source)) { if (key === "titleSource") { continue; } From c6e97d4f98a442d097f90b9d8b5f8da559d084e9 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 16:24:07 +0100 Subject: [PATCH 04/10] Fix nullable result types in all 4 SDK codegens The runtime schema uses anyOf [{not:{}}, {$ref: ...}] for optional results (e.g., writeFile returns SessionFsError or undefined). Previously all 4 codegen scripts only recognized {type: 'null'} as null-like, producing incorrect wrapper types. Changes: - utils.ts: Add getNullableInner() to detect {not:{}} null patterns - typescript.ts: Emit 'T | undefined' for nullable results - csharp.ts: Emit 'Task' for nullable results - python.ts: Emit 'T | None' for nullable results - go.ts: Emit '*T' (pointer) for nullable results - Regenerated all 4 languages - Fixed C# test handler to use Task - Fixed Go session.go Vision type rename Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Generated/Rpc.cs | 24 +++-- dotnet/src/Generated/SessionEvents.cs | 10 ++ dotnet/test/SessionFsTests.cs | 86 ++++++++++++++-- go/generated_session_events.go | 4 + go/internal/e2e/session_fs_test.go | 112 +++++++++++++++++--- go/rpc/generated_rpc.go | 1 + go/session.go | 2 +- nodejs/src/generated/rpc.ts | 11 +- nodejs/src/generated/session-events.ts | 4 + python/copilot/generated/rpc.py | 26 +++-- python/copilot/generated/session_events.py | 10 ++ python/e2e/test_session_fs.py | 54 ++++++++++ scripts/codegen/csharp.ts | 58 +++++++---- scripts/codegen/go.ts | 39 ++++++- scripts/codegen/python.ts | 114 +++++++++++++++------ scripts/codegen/typescript.ts | 26 ++++- scripts/codegen/utils.ts | 22 ++++ 17 files changed, 491 insertions(+), 112 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 61cc19fc1..4ebd19df9 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -722,6 +722,10 @@ public sealed class WorkspacesGetWorkspaceResultWorkspace [JsonPropertyName("updated_at")] public DateTimeOffset? UpdatedAt { get; set; } + /// Gets or sets the remote_steerable value. + [JsonPropertyName("remote_steerable")] + public bool? RemoteSteerable { get; set; } + /// Gets or sets the mc_task_id value. [JsonPropertyName("mc_task_id")] public string? McTaskId { get; set; } @@ -3105,23 +3109,23 @@ public interface ISessionFsHandler /// Handles "sessionFs.readFile". Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.writeFile". - Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); + Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.appendFile". - Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); + Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.exists". Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.stat". Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.mkdir". - Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); + Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdir". Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.readdirWithTypes". Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rm". - Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); + Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default); /// Handles "sessionFs.rename". - Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); + Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); } /// Provides all client session API handler groups for a session. @@ -3151,7 +3155,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsWriteFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -3161,7 +3165,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsAppendFileMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -3191,7 +3195,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsMkdirMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -3221,7 +3225,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsRmMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); @@ -3231,7 +3235,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, Func>)(async (request, cancellationToken) => + var registerSessionFsRenameMethod = (Func>)(async (request, cancellationToken) => { var handler = getHandlers(request.SessionId).SessionFs; if (handler is null) throw new InvalidOperationException($"No sessionFs handler registered for session: {request.SessionId}"); diff --git a/dotnet/src/Generated/SessionEvents.cs b/dotnet/src/Generated/SessionEvents.cs index 19bb5d665..85b21e23d 100644 --- a/dotnet/src/Generated/SessionEvents.cs +++ b/dotnet/src/Generated/SessionEvents.cs @@ -1491,6 +1491,11 @@ public partial class SessionContextChangedData [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } + /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } + /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("branch")] @@ -2710,6 +2715,11 @@ public partial class WorkingDirectoryContext [JsonPropertyName("hostType")] public WorkingDirectoryContextHostType? HostType { get; set; } + /// Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com"). + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("repositoryHost")] + public string? RepositoryHost { get; set; } + /// Current git branch name. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("branch")] diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs index 8c55b1120..1a89f4c09 100644 --- a/dotnet/test/SessionFsTests.cs +++ b/dotnet/test/SessionFsTests.cs @@ -227,6 +227,70 @@ await WaitForConditionAsync(async () => } } + [Fact] + public async Task Should_Write_Workspace_Metadata_Via_SessionFs() + { + var providerRoot = CreateProviderRoot(); + try + { + await using var client = CreateSessionFsClient(providerRoot); + var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + }); + + var msg = await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 7 * 8?" }); + Assert.Contains("56", msg?.Data.Content ?? string.Empty); + + // WorkspaceManager should have created workspace.yaml via sessionFs + var workspaceYamlPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/workspace.yaml"); + await WaitForConditionAsync(() => File.Exists(workspaceYamlPath)); + var yaml = await ReadAllTextSharedAsync(workspaceYamlPath); + Assert.Contains("id:", yaml); + + // Checkpoint index should also exist + var indexPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/checkpoints/index.md"); + await WaitForConditionAsync(() => File.Exists(indexPath)); + + await session.DisposeAsync(); + } + finally + { + await TryDeleteDirectoryAsync(providerRoot); + } + } + + [Fact] + public async Task Should_Persist_Plan_Md_Via_SessionFs() + { + var providerRoot = CreateProviderRoot(); + try + { + await using var client = CreateSessionFsClient(providerRoot); + var session = await client.CreateSessionAsync(new SessionConfig + { + OnPermissionRequest = PermissionHandler.ApproveAll, + CreateSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot), + }); + + // Write a plan via the session RPC + await session.SendAndWaitAsync(new MessageOptions { Prompt = "What is 2 + 3?" }); + await session.Rpc.Plan.UpdateAsync("# Test Plan\n\nThis is a test."); + + var planPath = GetStoredPath(providerRoot, session.SessionId, "/session-state/plan.md"); + await WaitForConditionAsync(() => File.Exists(planPath)); + var content = await ReadAllTextSharedAsync(planPath); + Assert.Contains("# Test Plan", content); + + await session.DisposeAsync(); + } + finally + { + await TryDeleteDirectoryAsync(providerRoot); + } + } + private CopilotClient CreateSessionFsClient(string providerRoot, bool useStdio = true) { Directory.CreateDirectory(providerRoot); @@ -375,18 +439,20 @@ public async Task ReadFileAsync(SessionFsReadFileReques return new SessionFsReadFileResult { Content = content }; } - public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) + public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); await File.WriteAllTextAsync(fullPath, request.Content, cancellationToken); + return null; } - public async Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default) + public async Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); await File.AppendAllTextAsync(fullPath, request.Content, cancellationToken); + return null; } public Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) @@ -430,10 +496,10 @@ public Task StatAsync(SessionFsStatRequest request, Cancell }); } - public Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) + public Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) { Directory.CreateDirectory(ResolvePath(request.Path)); - return Task.CompletedTask; + return Task.FromResult(null); } public Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) @@ -462,31 +528,31 @@ public Task ReaddirWithTypesAsync(SessionFsRead return Task.FromResult(new SessionFsReaddirWithTypesResult { Entries = entries }); } - public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) + public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) { var fullPath = ResolvePath(request.Path); if (File.Exists(fullPath)) { File.Delete(fullPath); - return Task.CompletedTask; + return Task.FromResult(null); } if (Directory.Exists(fullPath)) { Directory.Delete(fullPath, request.Recursive ?? false); - return Task.CompletedTask; + return Task.FromResult(null); } if (request.Force == true) { - return Task.CompletedTask; + return Task.FromResult(null); } throw new FileNotFoundException($"Path does not exist: {request.Path}"); } - public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) + public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) { var src = ResolvePath(request.Src); var dest = ResolvePath(request.Dest); @@ -501,7 +567,7 @@ public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancel File.Move(src, dest, overwrite: true); } - return Task.CompletedTask; + return Task.FromResult(null); } private string ResolvePath(string sessionPath) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index 95ace9123..ab1bf91a4 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -866,6 +866,8 @@ type SessionContextChangedData struct { Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") + RepositoryHost *string `json:"repositoryHost,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time @@ -1650,6 +1652,8 @@ type WorkingDirectoryContext struct { Repository *string `json:"repository,omitempty"` // Hosting platform type of the repository (github or ado) HostType *WorkingDirectoryContextHostType `json:"hostType,omitempty"` + // Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") + RepositoryHost *string `json:"repositoryHost,omitempty"` // Current git branch name Branch *string `json:"branch,omitempty"` // Head commit of current git branch at session start time diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go index 7fba219f7..5f4650a58 100644 --- a/go/internal/e2e/session_fs_test.go +++ b/go/internal/e2e/session_fs_test.go @@ -245,6 +245,90 @@ func TestSessionFs(t *testing.T) { t.Fatalf("Timed out waiting for checkpoint rewrite: %v", err) } }) + t.Run("should write workspace metadata via sessionFs", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + CreateSessionFsHandler: createSessionFsHandler, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + msg, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "What is 7 * 8?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + content := "" + if msg != nil { + if d, ok := msg.Data.(*copilot.AssistantMessageData); ok { + content = d.Content + } + } + if !strings.Contains(content, "56") { + t.Fatalf("Expected response to contain 56, got %q", content) + } + + // WorkspaceManager should have created workspace.yaml via sessionFs + workspaceYamlPath := p(session.SessionID, "/session-state/workspace.yaml") + if err := waitForFile(workspaceYamlPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for workspace.yaml: %v", err) + } + yaml, err := os.ReadFile(workspaceYamlPath) + if err != nil { + t.Fatalf("Failed to read workspace.yaml: %v", err) + } + if !strings.Contains(string(yaml), "id:") { + t.Fatalf("Expected workspace.yaml to contain 'id:', got %q", string(yaml)) + } + + // Checkpoint index should also exist + indexPath := p(session.SessionID, "/session-state/checkpoints/index.md") + if err := waitForFile(indexPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for checkpoints/index.md: %v", err) + } + + if err := session.Disconnect(); err != nil { + t.Fatalf("Failed to disconnect session: %v", err) + } + }) + + t.Run("should persist plan.md via sessionFs", func(t *testing.T) { + ctx.ConfigureForTest(t) + + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + CreateSessionFsHandler: createSessionFsHandler, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + // Write a plan via the session RPC + if _, err := session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "What is 2 + 3?"}); err != nil { + t.Fatalf("Failed to send message: %v", err) + } + if _, err := session.RPC.Plan.Update(t.Context(), &rpc.PlanUpdateRequest{Content: "# Test Plan\n\nThis is a test."}); err != nil { + t.Fatalf("Failed to update plan: %v", err) + } + + planPath := p(session.SessionID, "/session-state/plan.md") + if err := waitForFile(planPath, 5*time.Second); err != nil { + t.Fatalf("Timed out waiting for plan.md: %v", err) + } + planContent, err := os.ReadFile(planPath) + if err != nil { + t.Fatalf("Failed to read plan.md: %v", err) + } + if !strings.Contains(string(planContent), "# Test Plan") { + t.Fatalf("Expected plan.md to contain '# Test Plan', got %q", string(planContent)) + } + + if err := session.Disconnect(); err != nil { + t.Fatalf("Failed to disconnect session: %v", err) + } + }) } var sessionFsConfig = &copilot.SessionFsConfig{ @@ -266,7 +350,7 @@ func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileRequest) ( return &rpc.SessionFSReadFileResult{Content: string(content)}, nil } -func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSWriteFileResult, error) { +func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSError, error) { path := providerPath(h.root, h.sessionID, request.Path) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err @@ -275,10 +359,10 @@ func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) if request.Mode != nil { mode = os.FileMode(uint32(*request.Mode)) } - return &rpc.SessionFSWriteFileResult{}, os.WriteFile(path, []byte(request.Content), mode) + return nil, os.WriteFile(path, []byte(request.Content), mode) } -func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSAppendFileResult, error) { +func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSError, error) { path := providerPath(h.root, h.sessionID, request.Path) if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { return nil, err @@ -296,7 +380,7 @@ func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileReques if err != nil { return nil, err } - return &rpc.SessionFSAppendFileResult{}, nil + return nil, nil } func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { @@ -325,16 +409,16 @@ func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.Ses }, nil } -func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSMkdirResult, error) { +func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSError, error) { path := providerPath(h.root, h.sessionID, request.Path) mode := os.FileMode(0o777) if request.Mode != nil { mode = os.FileMode(uint32(*request.Mode)) } if request.Recursive != nil && *request.Recursive { - return &rpc.SessionFSMkdirResult{}, os.MkdirAll(path, mode) + return nil, os.MkdirAll(path, mode) } - return &rpc.SessionFSMkdirResult{}, os.Mkdir(path, mode) + return nil, os.Mkdir(path, mode) } func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { @@ -368,28 +452,28 @@ func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWit return &rpc.SessionFSReaddirWithTypesResult{Entries: result}, nil } -func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSRmResult, error) { +func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSError, error) { path := providerPath(h.root, h.sessionID, request.Path) if request.Recursive != nil && *request.Recursive { err := os.RemoveAll(path) if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return &rpc.SessionFSRmResult{}, nil + return nil, nil } - return &rpc.SessionFSRmResult{}, err + return nil, err } err := os.Remove(path) if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return &rpc.SessionFSRmResult{}, nil + return nil, nil } - return &rpc.SessionFSRmResult{}, err + return nil, err } -func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSRenameResult, error) { +func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSError, error) { dest := providerPath(h.root, h.sessionID, request.Dest) if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { return nil, err } - return &rpc.SessionFSRenameResult{}, os.Rename( + return nil, os.Rename( providerPath(h.root, h.sessionID, request.Src), dest, ) diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 451b5db9f..85370b64e 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -592,6 +592,7 @@ type WorkspaceClass struct { McSessionID *string `json:"mc_session_id,omitempty"` McTaskID *string `json:"mc_task_id,omitempty"` Name *string `json:"name,omitempty"` + RemoteSteerable *bool `json:"remote_steerable,omitempty"` Repository *string `json:"repository,omitempty"` SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` Summary *string `json:"summary,omitempty"` diff --git a/go/session.go b/go/session.go index bf42bf03a..f40108aaf 100644 --- a/go/session.go +++ b/go/session.go @@ -1241,7 +1241,7 @@ func convertModelCapabilitiesToClass(src *rpc.ModelCapabilitiesOverride) *rpc.Mo MaxPromptTokens: src.Limits.MaxPromptTokens, } if src.Limits.Vision != nil { - dst.Limits.Vision = &rpc.FluffyModelCapabilitiesOverrideLimitsVision{ + dst.Limits.Vision = &rpc.PurpleModelCapabilitiesOverrideLimitsVision{ MaxPromptImageSize: src.Limits.Vision.MaxPromptImageSize, MaxPromptImages: src.Limits.Vision.MaxPromptImages, SupportedMediaTypes: src.Limits.Vision.SupportedMediaTypes, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index 94f691777..ac1338d6f 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -805,6 +805,7 @@ export interface WorkspacesGetWorkspaceResult { summary_count?: number; created_at?: string; updated_at?: string; + remote_steerable?: boolean; mc_task_id?: string; mc_session_id?: string; mc_last_event_id?: string; @@ -1778,15 +1779,15 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** Handler for `sessionFs` client session API methods. */ export interface SessionFsHandler { readFile(params: SessionFsReadFileRequest): Promise; - writeFile(params: SessionFsWriteFileRequest): Promise; - appendFile(params: SessionFsAppendFileRequest): Promise; + writeFile(params: SessionFsWriteFileRequest): Promise; + appendFile(params: SessionFsAppendFileRequest): Promise; exists(params: SessionFsExistsRequest): Promise; stat(params: SessionFsStatRequest): Promise; - mkdir(params: SessionFsMkdirRequest): Promise; + mkdir(params: SessionFsMkdirRequest): Promise; readdir(params: SessionFsReaddirRequest): Promise; readdirWithTypes(params: SessionFsReaddirWithTypesRequest): Promise; - rm(params: SessionFsRmRequest): Promise; - rename(params: SessionFsRenameRequest): Promise; + rm(params: SessionFsRmRequest): Promise; + rename(params: SessionFsRenameRequest): Promise; } /** All client session API handler groups. */ diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 64bb0a95a..4a5b12ad2 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -3969,6 +3969,10 @@ export interface WorkingDirectoryContext { * Hosting platform type of the repository (github or ado) */ hostType?: "github" | "ado"; + /** + * Raw host string from the git remote URL (e.g. "github.com", "mycompany.ghe.com", "dev.azure.com") + */ + repositoryHost?: string; /** * Current git branch name */ diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 6bfe8258d..cf9d82ea0 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -2502,6 +2502,7 @@ class Workspace: mc_session_id: str | None = None mc_task_id: str | None = None name: str | None = None + remote_steerable: bool | None = None repository: str | None = None session_sync_level: SessionSyncLevel | None = None summary: str | None = None @@ -2522,12 +2523,13 @@ def from_dict(obj: Any) -> 'Workspace': mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) name = from_union([from_str, from_none], obj.get("name")) + remote_steerable = from_union([from_bool, from_none], obj.get("remote_steerable")) repository = from_union([from_str, from_none], obj.get("repository")) session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) summary = from_union([from_str, from_none], obj.get("summary")) summary_count = from_union([from_int, from_none], obj.get("summary_count")) updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, repository, session_sync_level, summary, summary_count, updated_at) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at) def to_dict(self) -> dict: result: dict = {} @@ -2552,6 +2554,8 @@ def to_dict(self) -> dict: result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) + if self.remote_steerable is not None: + result["remote_steerable"] = from_union([from_bool, from_none], self.remote_steerable) if self.repository is not None: result["repository"] = from_union([from_str, from_none], self.repository) if self.session_sync_level is not None: @@ -4673,23 +4677,23 @@ async def log(self, params: LogRequest, *, timeout: float | None = None) -> LogR class SessionFsHandler(Protocol): async def read_file(self, params: SessionFSReadFileRequest) -> SessionFSReadFileResult: pass - async def write_file(self, params: SessionFSWriteFileRequest) -> SessionFSError: + async def write_file(self, params: SessionFSWriteFileRequest) -> SessionFSError | None: pass - async def append_file(self, params: SessionFSAppendFileRequest) -> SessionFSError: + async def append_file(self, params: SessionFSAppendFileRequest) -> SessionFSError | None: pass async def exists(self, params: SessionFSExistsRequest) -> SessionFSExistsResult: pass async def stat(self, params: SessionFSStatRequest) -> SessionFSStatResult: pass - async def mkdir(self, params: SessionFSMkdirRequest) -> SessionFSError: + async def mkdir(self, params: SessionFSMkdirRequest) -> SessionFSError | None: pass async def readdir(self, params: SessionFSReaddirRequest) -> SessionFSReaddirResult: pass async def readdir_with_types(self, params: SessionFSReaddirWithTypesRequest) -> SessionFSReaddirWithTypesResult: pass - async def rm(self, params: SessionFSRmRequest) -> SessionFSError: + async def rm(self, params: SessionFSRmRequest) -> SessionFSError | None: pass - async def rename(self, params: SessionFSRenameRequest) -> SessionFSError: + async def rename(self, params: SessionFSRenameRequest) -> SessionFSError | None: pass @dataclass @@ -4713,14 +4717,14 @@ async def handle_session_fs_write_file(params: dict) -> dict | None: handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.write_file(request) - return result.to_dict() + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.writeFile", handle_session_fs_write_file) async def handle_session_fs_append_file(params: dict) -> dict | None: request = SessionFSAppendFileRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.append_file(request) - return result.to_dict() + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.appendFile", handle_session_fs_append_file) async def handle_session_fs_exists(params: dict) -> dict | None: request = SessionFSExistsRequest.from_dict(params) @@ -4741,7 +4745,7 @@ async def handle_session_fs_mkdir(params: dict) -> dict | None: handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.mkdir(request) - return result.to_dict() + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.mkdir", handle_session_fs_mkdir) async def handle_session_fs_readdir(params: dict) -> dict | None: request = SessionFSReaddirRequest.from_dict(params) @@ -4762,12 +4766,12 @@ async def handle_session_fs_rm(params: dict) -> dict | None: handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.rm(request) - return result.to_dict() + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.rm", handle_session_fs_rm) async def handle_session_fs_rename(params: dict) -> dict | None: request = SessionFSRenameRequest.from_dict(params) handler = get_handlers(request.session_id).session_fs if handler is None: raise RuntimeError(f"No session_fs handler registered for session: {request.session_id}") result = await handler.rename(request) - return result.to_dict() + return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.rename", handle_session_fs_rename) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 7cbff3039..5992bf32d 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -258,6 +258,7 @@ class WorkingDirectoryContext: git_root: str | None = None repository: str | None = None host_type: WorkingDirectoryContextHostType | None = None + repository_host: str | None = None branch: str | None = None head_commit: str | None = None base_commit: str | None = None @@ -269,6 +270,7 @@ def from_dict(obj: Any) -> "WorkingDirectoryContext": git_root = from_union([from_none, from_str], obj.get("gitRoot")) repository = from_union([from_none, from_str], obj.get("repository")) host_type = from_union([from_none, lambda x: parse_enum(WorkingDirectoryContextHostType, x)], obj.get("hostType")) + repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) branch = from_union([from_none, from_str], obj.get("branch")) head_commit = from_union([from_none, from_str], obj.get("headCommit")) base_commit = from_union([from_none, from_str], obj.get("baseCommit")) @@ -277,6 +279,7 @@ def from_dict(obj: Any) -> "WorkingDirectoryContext": git_root=git_root, repository=repository, host_type=host_type, + repository_host=repository_host, branch=branch, head_commit=head_commit, base_commit=base_commit, @@ -291,6 +294,8 @@ def to_dict(self) -> dict: result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(WorkingDirectoryContextHostType, x)], self.host_type) + if self.repository_host is not None: + result["repositoryHost"] = from_union([from_none, from_str], self.repository_host) if self.branch is not None: result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: @@ -994,6 +999,7 @@ class SessionContextChangedData: git_root: str | None = None repository: str | None = None host_type: SessionContextChangedDataHostType | None = None + repository_host: str | None = None branch: str | None = None head_commit: str | None = None base_commit: str | None = None @@ -1005,6 +1011,7 @@ def from_dict(obj: Any) -> "SessionContextChangedData": git_root = from_union([from_none, from_str], obj.get("gitRoot")) repository = from_union([from_none, from_str], obj.get("repository")) host_type = from_union([from_none, lambda x: parse_enum(SessionContextChangedDataHostType, x)], obj.get("hostType")) + repository_host = from_union([from_none, from_str], obj.get("repositoryHost")) branch = from_union([from_none, from_str], obj.get("branch")) head_commit = from_union([from_none, from_str], obj.get("headCommit")) base_commit = from_union([from_none, from_str], obj.get("baseCommit")) @@ -1013,6 +1020,7 @@ def from_dict(obj: Any) -> "SessionContextChangedData": git_root=git_root, repository=repository, host_type=host_type, + repository_host=repository_host, branch=branch, head_commit=head_commit, base_commit=base_commit, @@ -1027,6 +1035,8 @@ def to_dict(self) -> dict: result["repository"] = from_union([from_none, from_str], self.repository) if self.host_type is not None: result["hostType"] = from_union([from_none, lambda x: to_enum(SessionContextChangedDataHostType, x)], self.host_type) + if self.repository_host is not None: + result["repositoryHost"] = from_union([from_none, from_str], self.repository_host) if self.branch is not None: result["branch"] = from_union([from_none, from_str], self.branch) if self.head_commit is not None: diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py index bc228707b..4ff473b38 100644 --- a/python/e2e/test_session_fs.py +++ b/python/e2e/test_session_fs.py @@ -214,6 +214,60 @@ def on_event(event: SessionEvent): await wait_for_content(events_path, "checkpointNumber") + async def test_should_write_workspace_metadata_via_sessionfs( + self, ctx: E2ETestContext, session_fs_client: CopilotClient + ): + provider_root = Path(ctx.work_dir) / "provider" + session = await session_fs_client.create_session( + on_permission_request=PermissionHandler.approve_all, + create_session_fs_handler=create_test_session_fs_handler(provider_root), + ) + + msg = await session.send_and_wait("What is 7 * 8?") + assert msg is not None + assert msg.data.content is not None + assert "56" in msg.data.content + + # WorkspaceManager should have created workspace.yaml via sessionFs + workspace_yaml_path = provider_path( + provider_root, session.session_id, "/session-state/workspace.yaml" + ) + await wait_for_path(workspace_yaml_path) + yaml_content = workspace_yaml_path.read_text(encoding="utf-8") + assert "id:" in yaml_content + + # Checkpoint index should also exist + index_path = provider_path( + provider_root, session.session_id, "/session-state/checkpoints/index.md" + ) + await wait_for_path(index_path) + + await session.disconnect() + + async def test_should_persist_plan_md_via_sessionfs( + self, ctx: E2ETestContext, session_fs_client: CopilotClient + ): + from copilot.generated.rpc import PlanUpdateRequest + + provider_root = Path(ctx.work_dir) / "provider" + session = await session_fs_client.create_session( + on_permission_request=PermissionHandler.approve_all, + create_session_fs_handler=create_test_session_fs_handler(provider_root), + ) + + # Write a plan via the session RPC + await session.send_and_wait("What is 2 + 3?") + await session.rpc.plan.update(PlanUpdateRequest(content="# Test Plan\n\nThis is a test.")) + + plan_path = provider_path( + provider_root, session.session_id, "/session-state/plan.md" + ) + await wait_for_path(plan_path) + content = plan_path.read_text(encoding="utf-8") + assert "# Test Plan" in content + + await session.disconnect() + class _SessionFsHandler: def __init__(self, provider_root: Path, session_id: str): diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index d9a4b0f96..10ecc5bef 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -30,6 +30,7 @@ import { isSchemaDeprecated, isObjectSchema, isVoidSchema, + getNullableInner, REPO_ROOT, type ApiSchema, type DefinitionCollections, @@ -150,12 +151,10 @@ function collectRpcMethods(node: Record): RpcMethod[] { } function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: Map): string { - if (schema.anyOf) { - const nonNull = schema.anyOf.filter((s) => typeof s === "object" && s.type !== "null"); - if (nonNull.length === 1 && typeof nonNull[0] === "object") { - // Pass required=true to get the base type, then add "?" for nullable - return schemaTypeToCSharp(nonNull[0] as JSONSchema7, true, knownTypes) + "?"; - } + const nullableInner = getNullableInner(schema); + if (nullableInner) { + // Pass required=true to get the base type, then add "?" for nullable + return schemaTypeToCSharp(nullableInner, true, knownTypes) + "?"; } if (schema.$ref) { const refName = schema.$ref.split("/").pop()!; @@ -561,16 +560,17 @@ function resolveSessionPropertyType( return resolveSessionPropertyType(refSchema, parentClassName, propName, isRequired, knownTypes, nestedClasses, enumOutput); } if (propSchema.anyOf) { - const hasNull = propSchema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); - const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null"); - if (nonNull.length === 1) { - return resolveSessionPropertyType(nonNull[0] as JSONSchema7, parentClassName, propName, isRequired && !hasNull, knownTypes, nestedClasses, enumOutput); + const simpleNullable = getNullableInner(propSchema); + if (simpleNullable) { + return resolveSessionPropertyType(simpleNullable, parentClassName, propName, false, knownTypes, nestedClasses, enumOutput); } // Discriminated union: anyOf with multiple object variants sharing a const discriminator + const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && s !== null && (s as JSONSchema7).type !== "null"); if (nonNull.length > 1) { const variants = nonNull as JSONSchema7[]; const discriminatorInfo = findDiscriminator(variants); if (discriminatorInfo) { + const hasNull = propSchema.anyOf.length > nonNull.length; const baseClassName = (propSchema.title as string) ?? `${parentClassName}${propName}`; const renamedBase = applyTypeRename(baseClassName); const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, knownTypes, nestedClasses, enumOutput, propSchema.description); @@ -578,7 +578,7 @@ function resolveSessionPropertyType( return isRequired && !hasNull ? renamedBase : `${renamedBase}?`; } } - return hasNull || !isRequired ? "object?" : "object"; + return !isRequired ? "object?" : "object"; } if (propSchema.enum && Array.isArray(propSchema.enum)) { const enumName = getOrCreateEnum(parentClassName, propName, propSchema.enum as string[], enumOutput, propSchema.description, propSchema.title as string | undefined, isSchemaDeprecated(propSchema)); @@ -787,6 +787,27 @@ function resultTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(getMethodResultSchema(method), `${typeToClassName(method.rpcMethod)}Result`); } +/** Returns the C# type for a method's result, accounting for nullable anyOf wrappers. */ +function resolvedResultTypeName(method: RpcMethod): string { + const schema = getMethodResultSchema(method); + if (!schema) return resultTypeName(method); + const inner = getNullableInner(schema); + if (inner) { + // Nullable wrapper: resolve the inner $ref type name with "?" suffix + const innerName = inner.$ref + ? typeToClassName(refTypeName(inner.$ref, rpcDefinitions)) + : getRpcSchemaTypeName(inner, resultTypeName(method)); + return `${innerName}?`; + } + return resultTypeName(method); +} + +/** Returns the Task or Task string for a method's result type. */ +function resultTaskType(method: RpcMethod): string { + const schema = getMethodResultSchema(method); + return !isVoidSchema(schema) ? `Task<${resolvedResultTypeName(method)}>` : "Task"; +} + function paramsTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(resolveMethodParamsSchema(method), `${typeToClassName(method.rpcMethod)}Request`); } @@ -833,13 +854,10 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam return resolveRpcType(refSchema, isRequired, parentClassName, propName, classes); } - // Handle anyOf: [T, null] → T? (nullable typed property) - if (schema.anyOf) { - const hasNull = schema.anyOf.some((s) => typeof s === "object" && (s as JSONSchema7).type === "null"); - const nonNull = schema.anyOf.filter((s) => typeof s === "object" && (s as JSONSchema7).type !== "null"); - if (nonNull.length === 1) { - return resolveRpcType(nonNull[0] as JSONSchema7, isRequired && !hasNull, parentClassName, propName, classes); - } + // Handle anyOf: [T, null/{not:{}}] → T? (nullable typed property) + const nullableInner = getNullableInner(schema); + if (nullableInner) { + return resolveRpcType(nullableInner, false, parentClassName, propName, classes); } // Handle enums (string unions like "interactive" | "plan" | "autopilot") if (schema.enum && Array.isArray(schema.enum)) { @@ -1337,7 +1355,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, const effectiveParams = resolveMethodParamsSchema(method); const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; const resultSchema = getMethodResultSchema(method); - const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; + const taskType = resultTaskType(method); lines.push(` /// Handles "${method.rpcMethod}".`); if (method.stability === "experimental" && !groupExperimental) { lines.push(` [Experimental(Diagnostics.Experimental)]`); @@ -1385,7 +1403,7 @@ function emitClientSessionApiRegistration(clientSchema: Record, const hasParams = !!effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0; const resultSchema = getMethodResultSchema(method); const paramsClass = paramsTypeName(method); - const taskType = !isVoidSchema(resultSchema) ? `Task<${resultTypeName(method)}>` : "Task"; + const taskType = resultTaskType(method); const registrationVar = `register${typeToClassName(method.rpcMethod)}Method`; if (hasParams) { diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index 102cbf7a6..e1906a62d 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -23,6 +23,7 @@ import { isNodeFullyDeprecated, isSchemaDeprecated, isVoidSchema, + getNullableInner, isRpcMethod, postProcessSchema, writeGeneratedFile, @@ -274,6 +275,14 @@ function goResultTypeName(method: RpcMethod): string { return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); } +function goNullableResultTypeName(method: RpcMethod, innerSchema: JSONSchema7): string { + if (innerSchema.$ref) { + const refName = innerSchema.$ref.split("/").pop(); + if (refName) return toPascalCase(refName); + } + return getRpcSchemaTypeName(innerSchema, toPascalCase(method.rpcMethod) + "Result"); +} + function goParamsTypeName(method: RpcMethod): string { const fallback = goRequestFallbackName(method); if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { @@ -436,6 +445,17 @@ function resolveGoPropertyType( // Handle anyOf if (propSchema.anyOf) { + const nullableInnerSchema = getNullableInner(propSchema); + if (nullableInnerSchema) { + // anyOf [T, null/{not:{}}] → nullable T + const innerType = resolveGoPropertyType(nullableInnerSchema, parentTypeName, jsonPropName, true, ctx); + if (isRequired) return innerType; + // Pointer-wrap if not already a pointer, slice, or map + if (innerType.startsWith("*") || innerType.startsWith("[]") || innerType.startsWith("map[")) { + return innerType; + } + return `*${innerType}`; + } const nonNull = (propSchema.anyOf as JSONSchema7[]).filter((s) => s.type !== "null"); const hasNull = (propSchema.anyOf as JSONSchema7[]).some((s) => s.type === "null"); @@ -443,7 +463,6 @@ function resolveGoPropertyType( // 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; } @@ -997,7 +1016,11 @@ async function generateRpc(schemaPath?: string): Promise { for (const method of allMethods) { const resultSchema = getMethodResultSchema(method); - if (isVoidSchema(resultSchema)) { + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + if (nullableInner) { + // Nullable results (e.g., *SessionFSError) don't need a wrapper type; + // the inner type is already in definitions via shared hoisting. + } else if (isVoidSchema(resultSchema)) { // Emit an empty struct for void results (forward-compatible with adding fields later) combinedSchema.definitions![goResultTypeName(method)] = { title: goResultTypeName(method), @@ -1286,7 +1309,11 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fieldNames: Map>, groupExperimental = false, isWrapper = false, groupDeprecated = false): void { const methodName = toPascalCase(name); - const resultType = resolveType(goResultTypeName(method)); + const resultSchema = getMethodResultSchema(method); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const resultType = nullableInner + ? resolveType(goNullableResultTypeName(method, nullableInner)) + : resolveType(goResultTypeName(method)); const effectiveParams = getMethodParamsSchema(method); const paramProps = effectiveParams?.properties || {}; @@ -1397,7 +1424,11 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record< lines.push(`\t// Experimental: ${clientHandlerMethodName(method.rpcMethod)} is an experimental API and may change or be removed in future versions.`); } const paramsType = resolveType(goParamsTypeName(method)); - const resultType = resolveType(goResultTypeName(method)); + const resultSchema = getMethodResultSchema(method); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const resultType = nullableInner + ? resolveType(goNullableResultTypeName(method, nullableInner)) + : resolveType(goResultTypeName(method)); lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (*${resultType}, error)`); } lines.push(`}`); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 87619c09e..36f5ff0d5 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -19,6 +19,7 @@ import { hoistTitledSchemas, isObjectSchema, isVoidSchema, + getNullableInner, isRpcMethod, isNodeFullyExperimental, isNodeFullyDeprecated, @@ -428,8 +429,14 @@ function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined { ); } -function pythonResultTypeName(method: RpcMethod): string { - return getRpcSchemaTypeName(getMethodResultSchema(method), toPascalCase(method.rpcMethod) + "Result"); +function pythonResultTypeName(method: RpcMethod, schemaOverride?: JSONSchema7): string { + const schema = schemaOverride ?? getMethodResultSchema(method); + // If schema is a $ref, derive the type name from the ref path + if (schema?.$ref) { + const refName = schema.$ref.split("/").pop(); + if (refName) return toPascalCase(refName); + } + return getRpcSchemaTypeName(schema, toPascalCase(method.rpcMethod) + "Result"); } function pythonParamsTypeName(method: RpcMethod): string { @@ -1576,10 +1583,14 @@ async function generateRpc(schemaPath?: string): Promise { for (const method of allMethods) { const resultSchema = getMethodResultSchema(method); if (!isVoidSchema(resultSchema)) { - combinedSchema.definitions![pythonResultTypeName(method)] = withRootTitle( - schemaSourceForNamedDefinition(method.result, resultSchema), - pythonResultTypeName(method) - ); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + if (!nullableInner) { + combinedSchema.definitions![pythonResultTypeName(method)] = withRootTitle( + schemaSourceForNamedDefinition(method.result, resultSchema), + pythonResultTypeName(method) + ); + } + // For nullable results, the inner type (e.g., SessionFsError) is already in definitions } const resolvedParams = getMethodParamsSchema(method); if (method.params && hasSchemaPayload(resolvedParams)) { @@ -1886,9 +1897,21 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, groupExperimental = false, groupDeprecated = false): void { const methodName = toSnakeCase(name); const resultSchema = getMethodResultSchema(method); - const hasResult = !isVoidSchema(resultSchema); - const resultType = hasResult ? resolveType(pythonResultTypeName(method)) : "None"; - const resultIsObject = isObjectSchema(resultSchema); + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const effectiveResultSchema = nullableInner ?? resultSchema; + const hasResult = !isVoidSchema(resultSchema) && !nullableInner; + const hasNullableResult = !!nullableInner; + const resultIsObject = isObjectSchema(effectiveResultSchema); + + let resultType: string; + if (hasNullableResult) { + const innerTypeName = resolveType(pythonResultTypeName(method, nullableInner)); + resultType = `${innerTypeName} | None`; + } else if (hasResult) { + resultType = resolveType(pythonResultTypeName(method)); + } else { + resultType = "None"; + } const effectiveParams = getMethodParamsSchema(method); const paramProps = effectiveParams?.properties || {}; @@ -1910,40 +1933,46 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession: lines.push(` """.. warning:: This API is experimental and may change or be removed in future versions."""`); } - // For object results use .from_dict(); for enums/primitives use direct construction - const deserialize = (expr: string) => resultIsObject ? `${resultType}.from_dict(${expr})` : `${resultType}(${expr})`; + // Deserialize helper + const innerTypeName = hasNullableResult ? resolveType(pythonResultTypeName(method, nullableInner)) : resultType; + const deserialize = (expr: string) => { + if (hasNullableResult) { + return resultIsObject + ? `${innerTypeName}.from_dict(${expr}) if ${expr} is not None else None` + : `${innerTypeName}(${expr}) if ${expr} is not None else None`; + } + return resultIsObject ? `${innerTypeName}.from_dict(${expr})` : `${innerTypeName}(${expr})`; + }; // Build request body with proper serialization/deserialization + const emitRequestCall = (paramsExpr: string) => { + const callExpr = `await self._client.request("${method.rpcMethod}", ${paramsExpr}, **_timeout_kwargs(timeout))`; + if (hasResult || hasNullableResult) { + if (hasNullableResult) { + lines.push(` _result = ${callExpr}`); + lines.push(` return ${deserialize("_result")}`); + } else { + lines.push(` return ${deserialize(callExpr)}`); + } + } else { + lines.push(` ${callExpr}`); + } + }; + if (isSession) { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); lines.push(` params_dict["sessionId"] = self._session_id`); - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); - } + emitRequestCall("params_dict"); } else { - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))`); - } + emitRequestCall(`{"sessionId": self._session_id}`); } } else { if (hasParams) { lines.push(` params_dict = {k: v for k, v in params.to_dict().items() if v is not None}`); - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", params_dict, **_timeout_kwargs(timeout))`); - } + emitRequestCall("params_dict"); } else { - if (hasResult) { - lines.push(` return ${deserialize(`await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`)}`); - } else { - lines.push(` await self._client.request("${method.rpcMethod}", {}, **_timeout_kwargs(timeout))`); - } + emitRequestCall("{}"); } } lines.push(``); @@ -2019,7 +2048,15 @@ function emitClientSessionHandlerMethod( ): void { const paramsType = resolveType(pythonParamsTypeName(method)); const resultSchema = getMethodResultSchema(method); - const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : "None"; + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + let resultType: string; + if (nullableInner) { + resultType = `${resolveType(pythonResultTypeName(method, nullableInner))} | None`; + } else if (!isVoidSchema(resultSchema)) { + resultType = resolveType(pythonResultTypeName(method)); + } else { + resultType = "None"; + } lines.push(` async def ${toSnakeCase(name)}(self, params: ${paramsType}) -> ${resultType}:`); if (method.deprecated && !groupDeprecated) { lines.push(` """.. deprecated:: This API is deprecated and will be removed in a future version."""`); @@ -2040,7 +2077,8 @@ function emitClientSessionRegistrationMethod( const handlerVariableName = `handle_${toSnakeCase(groupName)}_${toSnakeCase(methodName)}`; const paramsType = resolveType(pythonParamsTypeName(method)); const resultSchema = getMethodResultSchema(method); - const resultType = !isVoidSchema(resultSchema) ? resolveType(pythonResultTypeName(method)) : null; + const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined; + const hasResult = !isVoidSchema(resultSchema) && !nullableInner; const handlerField = toSnakeCase(groupName); const handlerMethod = toSnakeCase(methodName); @@ -2050,13 +2088,21 @@ function emitClientSessionRegistrationMethod( lines.push( ` if handler is None: raise RuntimeError(f"No ${handlerField} handler registered for session: {request.session_id}")` ); - if (resultType) { + if (hasResult) { lines.push(` result = await handler.${handlerMethod}(request)`); if (isObjectSchema(resultSchema)) { lines.push(` return result.to_dict()`); } else { lines.push(` return result.value if hasattr(result, 'value') else result`); } + } else if (nullableInner) { + lines.push(` result = await handler.${handlerMethod}(request)`); + const resolvedInner = resolveSchema(nullableInner, rpcDefinitions) ?? nullableInner; + if (isObjectSchema(resolvedInner) || nullableInner.$ref) { + lines.push(` return result.to_dict() if result is not None else None`); + } else { + lines.push(` return result`); + } } else { lines.push(` await handler.${handlerMethod}(request)`); lines.push(` return None`); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index d9adaab57..4eb33e4d9 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -12,6 +12,7 @@ import { compile } from "json-schema-to-typescript"; import { getApiSchemaPath, fixNullableRequiredRefsInApiSchema, + getNullableInner, getRpcSchemaTypeName, getSessionEventsSchemaPath, normalizeSchemaTitles, @@ -318,6 +319,25 @@ function resultTypeName(method: RpcMethod): string { ); } +function tsNullableResultTypeName(method: RpcMethod): string | undefined { + const resultSchema = getMethodResultSchema(method); + if (!resultSchema) return undefined; + const inner = getNullableInner(resultSchema); + if (!inner) return undefined; + // Resolve $ref to a type name + if (inner.$ref) { + const refName = inner.$ref.split("/").pop(); + if (refName) return `${toPascalCase(refName)} | undefined`; + } + const innerName = getRpcSchemaTypeName(inner, method.rpcMethod.split(".").map(toPascalCase).join("") + "Result"); + return `${innerName} | undefined`; +} + +function tsResultType(method: RpcMethod): string { + if (isVoidSchema(getMethodResultSchema(method))) return "void"; + return tsNullableResultTypeName(method) ?? resultTypeName(method); +} + function paramsTypeName(method: RpcMethod): string { const fallback = rpcRequestFallbackName(method); if (method.rpcMethod.startsWith("session.") && method.params?.$ref) { @@ -363,7 +383,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; for (const method of [...allMethods, ...clientSessionMethods]) { const resultSchema = getMethodResultSchema(method); - if (!isVoidSchema(resultSchema)) { + if (!isVoidSchema(resultSchema) && !getNullableInner(resultSchema)) { combinedSchema.definitions![resultTypeName(method)] = withRootTitle( schemaSourceForNamedDefinition(method.result, resultSchema), resultTypeName(method) @@ -486,7 +506,7 @@ function emitGroup(node: Record, indent: string, isSession: boo for (const [key, value] of Object.entries(node)) { if (isRpcMethod(value)) { const { rpcMethod, params } = value; - const resultType = !isVoidSchema(getMethodResultSchema(value)) ? resultTypeName(value) : "void"; + const resultType = tsResultType(value); const paramsType = paramsTypeName(value); const effectiveParams = getMethodParamsSchema(value); @@ -591,7 +611,7 @@ function emitClientSessionApiRegistration(clientSchema: Record) const name = handlerMethodName(method.rpcMethod); const hasParams = hasSchemaPayload(getMethodParamsSchema(method)); const pType = hasParams ? paramsTypeName(method) : ""; - const rType = !isVoidSchema(getMethodResultSchema(method)) ? resultTypeName(method) : "void"; + const rType = tsResultType(method); if (method.deprecated && !groupDeprecated) { lines.push(` /** @deprecated */`); diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts index 063857d11..4374b5a5a 100644 --- a/scripts/codegen/utils.ts +++ b/scripts/codegen/utils.ts @@ -245,6 +245,28 @@ export function isVoidSchema(schema: JSONSchema7 | null | undefined): boolean { return schema.type === "null"; } +/** + * If the schema is a nullable anyOf (anyOf: [nullLike, T] or [T, nullLike]), + * returns the non-null inner schema. Recognizes both `{ type: "null" }` and + * `{ not: {} }` (zod-to-json-schema 2019-09 format for undefined). + * Returns undefined if the schema is not a nullable wrapper. + */ +export function getNullableInner(schema: JSONSchema7): JSONSchema7 | undefined { + if (!schema.anyOf || !Array.isArray(schema.anyOf) || schema.anyOf.length !== 2) return undefined; + const [a, b] = schema.anyOf; + if (isNullLike(a) && !isNullLike(b)) return b as JSONSchema7; + if (isNullLike(b) && !isNullLike(a)) return a as JSONSchema7; + return undefined; +} + +function isNullLike(s: unknown): boolean { + if (!s || typeof s !== "object") return false; + const obj = s as Record; + if (obj.type === "null") return true; + if ("not" in obj && typeof obj.not === "object" && obj.not !== null && Object.keys(obj.not).length === 0) return true; + return false; +} + export function cloneSchemaForCodegen(value: T): T { if (Array.isArray(value)) { return value.map((item) => cloneSchemaForCodegen(item)) as T; From 2209a43f96a15fbaa074693ab0792ada84a2c90c Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 16:54:34 +0100 Subject: [PATCH 05/10] More codegen fixes --- go/generated_session_events.go | 6 +- go/rpc/generated_rpc.go | 1954 ++++++++++++++++++-------------- go/session.go | 48 +- go/types.go | 2 +- scripts/codegen/go.ts | 33 +- 5 files changed, 1159 insertions(+), 884 deletions(-) diff --git a/go/generated_session_events.go b/go/generated_session_events.go index ab1bf91a4..02c3282f9 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -1945,7 +1945,7 @@ type PermissionRequestShellCommand struct { ReadOnly bool `json:"readOnly"` } -type PermissionRequestShellPossibleUrl struct { +type PermissionRequestShellPossibleURL struct { // URL that may be accessed by the command URL string `json:"url"` } @@ -1965,7 +1965,7 @@ type PermissionRequest struct { // 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 []PermissionRequestShellPossibleUrl `json:"possibleUrls,omitempty"` + PossibleUrls []PermissionRequestShellPossibleURL `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 @@ -2332,7 +2332,7 @@ const ( // Type aliases for convenience. type ( PermissionRequestCommand = PermissionRequestShellCommand - PossibleURL = PermissionRequestShellPossibleUrl + PossibleURL = PermissionRequestShellPossibleURL Attachment = UserMessageAttachment AttachmentType = UserMessageAttachmentType ) diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 85370b64e..d44135cfd 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -12,64 +12,249 @@ import ( "time" ) -type UIElicitationResponseContent map[string]*UIElicitationFieldValue +type RPCTypes struct { + AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` + AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` + AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` + AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` + AgentInfo AgentInfo `json:"AgentInfo"` + AgentList AgentList `json:"AgentList"` + AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` + AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` + AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` + CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` + CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` + CurrentModel CurrentModel `json:"CurrentModel"` + DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` + Extension Extension `json:"Extension"` + ExtensionList ExtensionList `json:"ExtensionList"` + ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` + ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` + ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` + ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` + ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` + FilterMapping *FilterMapping `json:"FilterMapping"` + FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` + FleetStartResult FleetStartResult `json:"FleetStartResult"` + HandleToolCallResult HandleToolCallResult `json:"HandleToolCallResult"` + HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` + HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` + HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` + HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` + InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` + InstructionsSources InstructionsSources `json:"InstructionsSources"` + LogRequest LogRequest `json:"LogRequest"` + LogResult LogResult `json:"LogResult"` + MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` + MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` + MCPConfigList MCPConfigList `json:"McpConfigList"` + MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` + MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` + MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` + MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` + MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` + MCPDisableResult MCPDisableResult `json:"McpDisableResult"` + MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` + MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` + MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` + MCPEnableResult MCPEnableResult `json:"McpEnableResult"` + MCPReloadResult MCPReloadResult `json:"McpReloadResult"` + MCPServer MCPServer `json:"McpServer"` + MCPServerConfig MCPServerConfig `json:"McpServerConfig"` + MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` + MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` + MCPServerList MCPServerList `json:"McpServerList"` + Model ModelElement `json:"Model"` + ModelBilling ModelBilling `json:"ModelBilling"` + ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` + ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` + ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` + ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` + ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` + ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` + ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` + ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` + ModelList ModelList `json:"ModelList"` + ModelPolicy ModelPolicy `json:"ModelPolicy"` + ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` + ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` + ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` + ModeSetResult ModeSetResult `json:"ModeSetResult"` + NameGetResult NameGetResult `json:"NameGetResult"` + NameSetRequest NameSetRequest `json:"NameSetRequest"` + NameSetResult NameSetResult `json:"NameSetResult"` + PermissionDecision PermissionDecision `json:"PermissionDecision"` + PermissionDecisionApproved PermissionDecisionApproved `json:"PermissionDecisionApproved"` + PermissionDecisionDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicy `json:"PermissionDecisionDeniedByContentExclusionPolicy"` + PermissionDecisionDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHook `json:"PermissionDecisionDeniedByPermissionRequestHook"` + PermissionDecisionDeniedByRules PermissionDecisionDeniedByRules `json:"PermissionDecisionDeniedByRules"` + PermissionDecisionDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUser `json:"PermissionDecisionDeniedInteractivelyByUser"` + PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser `json:"PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"` + PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` + PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` + PingRequest PingRequest `json:"PingRequest"` + PingResult PingResult `json:"PingResult"` + PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` + PlanReadResult PlanReadResult `json:"PlanReadResult"` + PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` + PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` + Plugin PluginElement `json:"Plugin"` + PluginList PluginList `json:"PluginList"` + ServerSkill ServerSkill `json:"ServerSkill"` + ServerSkillList ServerSkillList `json:"ServerSkillList"` + SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` + SessionFSError SessionFSError `json:"SessionFsError"` + SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` + SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` + SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` + SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` + SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` + SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` + SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` + SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` + SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` + SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` + SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` + SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` + SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` + SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` + SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` + SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` + SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` + SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` + SessionMode SessionMode `json:"SessionMode"` + SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` + SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` + ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` + ShellExecResult ShellExecResult `json:"ShellExecResult"` + ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` + ShellKillResult ShellKillResult `json:"ShellKillResult"` + Skill Skill `json:"Skill"` + SkillList SkillList `json:"SkillList"` + SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` + SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` + SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` + SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` + SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` + SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` + SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` + SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` + Tool Tool `json:"Tool"` + ToolCallResult ToolCallResult `json:"ToolCallResult"` + ToolList ToolList `json:"ToolList"` + ToolsHandlePendingToolCallRequest ToolsHandlePendingToolCallRequest `json:"ToolsHandlePendingToolCallRequest"` + ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` + UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` + UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` + UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` + UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` + UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` + UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` + UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` + UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` + UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` + UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` + UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` + UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` + UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` + UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` + UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` + UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` + UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` + UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` + UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` + UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` + UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` + UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` + UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` + UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` + WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` + WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` + WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` + WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` + WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` + WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` +} -// Model capabilities and limits -type ModelCapabilities struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` +type AccountGetQuotaResult struct { + // Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) + QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesLimits struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - // Maximum number of output/completion tokens - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - // Maximum number of prompt/input tokens - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - // Vision-specific limits - Vision *ModelCapabilitiesLimitsVision `json:"vision,omitempty"` +type AccountQuotaSnapshot struct { + // Number of requests included in the entitlement + EntitlementRequests int64 `json:"entitlementRequests"` + // Number of overage requests made this period + Overage int64 `json:"overage"` + // Whether pay-per-request usage is allowed when quota is exhausted + OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` + // Percentage of entitlement remaining + RemainingPercentage float64 `json:"remainingPercentage"` + // Date when the quota resets (ISO 8601) + ResetDate *time.Time `json:"resetDate,omitempty"` + // Number of requests used so far this period + UsedRequests int64 `json:"usedRequests"` } -// Feature flags indicating what the model supports -type ModelCapabilitiesSupports struct { - // Whether this model supports reasoning effort configuration - ReasoningEffort *bool `json:"reasoningEffort,omitempty"` - // Whether this model supports vision/image input - Vision *bool `json:"vision,omitempty"` +// Experimental: AgentDeselectResult is part of an experimental API and may change or be removed. +type AgentDeselectResult struct { } -// Vision-specific limits -type ModelCapabilitiesLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize int64 `json:"max_prompt_image_size"` - // Maximum number of images per prompt - MaxPromptImages int64 `json:"max_prompt_images"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types"` +// Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. +type AgentGetCurrentResult struct { + // Currently selected custom agent, or null if using the default agent + Agent *AgentInfo `json:"agent"` } -// MCP server configuration (local/stdio or remote/http) -type MCPServerConfig struct { - Args []string `json:"args,omitempty"` - Command *string `json:"command,omitempty"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping *FilterMapping `json:"filterMapping"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - // Timeout in milliseconds for tool calls to this server. - Timeout *int64 `json:"timeout,omitempty"` - // Tools to include. Defaults to all tools if not specified. - Tools []string `json:"tools,omitempty"` - // Remote transport type. Defaults to "http" when omitted. - Type *MCPServerConfigType `json:"type,omitempty"` - Headers map[string]string `json:"headers,omitempty"` - OauthClientID *string `json:"oauthClientId,omitempty"` - OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` - URL *string `json:"url,omitempty"` +// The newly selected custom agent +type AgentInfo struct { + // Description of the agent's purpose + Description string `json:"description"` + // Human-readable display name + DisplayName string `json:"displayName"` + // Unique identifier of the custom agent + Name string `json:"name"` +} + +// Experimental: AgentList is part of an experimental API and may change or be removed. +type AgentList struct { + // Available custom agents + Agents []AgentInfo `json:"agents"` +} + +// Experimental: AgentReloadResult is part of an experimental API and may change or be removed. +type AgentReloadResult struct { + // Reloaded custom agents + Agents []AgentInfo `json:"agents"` +} + +// Experimental: AgentSelectRequest is part of an experimental API and may change or be removed. +type AgentSelectRequest struct { + // Name of the custom agent to select + Name string `json:"name"` +} + +// Experimental: AgentSelectResult is part of an experimental API and may change or be removed. +type AgentSelectResult struct { + // The newly selected custom agent + Agent AgentInfo `json:"agent"` +} + +type CommandsHandlePendingCommandRequest struct { + // Error message if the command handler failed + Error *string `json:"error,omitempty"` + // Request ID from the command invocation event + RequestID string `json:"requestId"` +} + +type CommandsHandlePendingCommandResult struct { + // Whether the command was handled successfully + Success bool `json:"success"` +} + +type CurrentModel struct { + // Currently active model identifier + ModelID *string `json:"modelId,omitempty"` } type DiscoveredMCPServer struct { @@ -83,248 +268,287 @@ type DiscoveredMCPServer struct { Type *DiscoveredMCPServerType `json:"type,omitempty"` } -type ServerSkillList struct { - // All discovered skills across all sources - Skills []ServerSkill `json:"skills"` +type Extension struct { + // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') + ID string `json:"id"` + // Extension name (directory name) + Name string `json:"name"` + // Process ID if the extension is running + PID *int64 `json:"pid,omitempty"` + // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) + Source ExtensionSource `json:"source"` + // Current status: running, disabled, failed, or starting + Status ExtensionStatus `json:"status"` } -type ServerSkill struct { - // Description of what the skill does - Description string `json:"description"` - // Whether the skill is currently enabled (based on global config) - Enabled bool `json:"enabled"` - // Unique identifier for the skill - Name string `json:"name"` - // Absolute path to the skill file - Path *string `json:"path,omitempty"` - // The project path this skill belongs to (only for project/inherited skills) - ProjectPath *string `json:"projectPath,omitempty"` - // Source location type (e.g., project, personal-copilot, plugin, builtin) - Source string `json:"source"` - // Whether the skill can be invoked by the user as a slash command - UserInvocable bool `json:"userInvocable"` +// Experimental: ExtensionList is part of an experimental API and may change or be removed. +type ExtensionList struct { + // Discovered extensions and their current status + Extensions []Extension `json:"extensions"` } -type CurrentModel struct { - // Currently active model identifier - ModelID *string `json:"modelId,omitempty"` +// Experimental: ExtensionsDisableRequest is part of an experimental API and may change or be removed. +type ExtensionsDisableRequest struct { + // Source-qualified extension ID to disable + ID string `json:"id"` } -// Override individual model capabilities resolved by the runtime -type ModelCapabilitiesOverride struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` +// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be removed. +type ExtensionsDisableResult struct { } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesOverrideLimits struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - Vision *PurpleModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` +// Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be removed. +type ExtensionsEnableRequest struct { + // Source-qualified extension ID to enable + ID string `json:"id"` } -type PurpleModelCapabilitiesOverrideLimitsVision struct { - // Maximum image size in bytes - MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` - // Maximum number of images per prompt - MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` - // MIME types the model accepts - SupportedMediaTypes []string `json:"supported_media_types,omitempty"` +// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be removed. +type ExtensionsEnableResult struct { } -// Feature flags indicating what the model supports -type ModelCapabilitiesOverrideSupports struct { - ReasoningEffort *bool `json:"reasoningEffort,omitempty"` - Vision *bool `json:"vision,omitempty"` +// Experimental: ExtensionsReloadResult is part of an experimental API and may change or be removed. +type ExtensionsReloadResult struct { } -type AgentInfo struct { - // Description of the agent's purpose - Description string `json:"description"` - // Human-readable display name - DisplayName string `json:"displayName"` - // Unique identifier of the custom agent - Name string `json:"name"` +// Experimental: FleetStartRequest is part of an experimental API and may change or be removed. +type FleetStartRequest struct { + // Optional user prompt to combine with fleet instructions + Prompt *string `json:"prompt,omitempty"` } -type MCPServerList struct { - // Configured MCP servers - Servers []MCPServer `json:"servers"` +// Experimental: FleetStartResult is part of an experimental API and may change or be removed. +type FleetStartResult struct { + // Whether fleet mode was successfully activated + Started bool `json:"started"` } -type MCPServer 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 *MCPServerSource `json:"source,omitempty"` - // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status MCPServerStatus `json:"status"` +type HandleToolCallResult struct { + // Whether the tool call result was handled successfully + Success bool `json:"success"` } -type ToolCallResult struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Type of the tool result - ResultType *string `json:"resultType,omitempty"` - // Text result to send back to the LLM - TextResultForLlm string `json:"textResultForLlm"` - // Telemetry data from tool execution - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` +// Post-compaction context window usage breakdown +type HistoryCompactContextWindow struct { + // Token count from non-system messages (user, assistant, tool) + ConversationTokens *int64 `json:"conversationTokens,omitempty"` + // Current total tokens in the context window (system + conversation + tool definitions) + CurrentTokens int64 `json:"currentTokens"` + // Current number of messages in the conversation + MessagesLength int64 `json:"messagesLength"` + // Token count from system message(s) + SystemTokens *int64 `json:"systemTokens,omitempty"` + // Maximum token count for the model's context window + TokenLimit int64 `json:"tokenLimit"` + // Token count from tool definitions + ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` } -type HandleToolCallResult struct { - // Whether the tool call result was handled successfully +// Experimental: HistoryCompactResult is part of an experimental API and may change or be removed. +type HistoryCompactResult struct { + // Post-compaction context window usage breakdown + ContextWindow *HistoryCompactContextWindow `json:"contextWindow,omitempty"` + // Number of messages removed during compaction + MessagesRemoved int64 `json:"messagesRemoved"` + // Whether compaction completed successfully Success bool `json:"success"` + // Number of tokens freed by compaction + TokensRemoved int64 `json:"tokensRemoved"` } -type UIElicitationStringEnumField struct { - Default *string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Enum []string `json:"enum"` - EnumNames []string `json:"enumNames,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationStringEnumFieldType `json:"type"` +// Experimental: HistoryTruncateRequest is part of an experimental API and may change or be removed. +type HistoryTruncateRequest struct { + // Event ID to truncate to. This event and all events after it are removed from the session. + EventID string `json:"eventId"` } -type UIElicitationStringOneOfField struct { - Default *string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf"` - Title *string `json:"title,omitempty"` - Type UIElicitationStringEnumFieldType `json:"type"` +// Experimental: HistoryTruncateResult is part of an experimental API and may change or be removed. +type HistoryTruncateResult struct { + // Number of events that were removed + EventsRemoved int64 `json:"eventsRemoved"` } -type UIElicitationStringOneOfFieldOneOf struct { - Const string `json:"const"` - Title string `json:"title"` +type InstructionsGetSourcesResult struct { + // Instruction sources for the session + Sources []InstructionsSources `json:"sources"` +} + +type InstructionsSources struct { + // Glob pattern from frontmatter — when set, this instruction applies only to matching files + ApplyTo *string `json:"applyTo,omitempty"` + // Raw content of the instruction file + Content string `json:"content"` + // Short description (body after frontmatter) for use in instruction tables + Description *string `json:"description,omitempty"` + // Unique identifier for this source (used for toggling) + ID string `json:"id"` + // Human-readable label + Label string `json:"label"` + // Where this source lives — used for UI grouping + Location InstructionsSourcesLocation `json:"location"` + // File path relative to repo or absolute for home + SourcePath string `json:"sourcePath"` + // Category of instruction source — used for merge logic + Type InstructionsSourcesType `json:"type"` +} + +type LogRequest struct { + // When true, the message is transient and not persisted to the session event log on disk + Ephemeral *bool `json:"ephemeral,omitempty"` + // Log severity level. Determines how the message is displayed in the timeline. Defaults to + // "info". + Level *SessionLogLevel `json:"level,omitempty"` + // Human-readable message + Message string `json:"message"` + // Optional URL the user can open in their browser for more details + URL *string `json:"url,omitempty"` +} + +type LogResult struct { + // The unique identifier of the emitted session event + EventID string `json:"eventId"` +} + +type MCPConfigAddRequest struct { + // MCP server configuration (local/stdio or remote/http) + Config MCPServerConfig `json:"config"` + // Unique name for the MCP server + Name string `json:"name"` +} + +// MCP server configuration (local/stdio or remote/http) +type MCPServerConfig struct { + Args []string `json:"args,omitempty"` + Command *string `json:"command,omitempty"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + // Remote transport type. Defaults to "http" when omitted. + Type *MCPServerConfigType `json:"type,omitempty"` + Headers map[string]string `json:"headers,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + URL *string `json:"url,omitempty"` +} + +type MCPConfigAddResult struct { +} + +type MCPConfigList struct { + // All MCP servers from user config, keyed by name + Servers map[string]MCPServerConfig `json:"servers"` +} + +type MCPConfigRemoveRequest struct { + // Name of the MCP server to remove + Name string `json:"name"` +} + +type MCPConfigRemoveResult struct { +} + +type MCPConfigUpdateRequest struct { + // MCP server configuration (local/stdio or remote/http) + Config MCPServerConfig `json:"config"` + // Name of the MCP server to update + Name string `json:"name"` } -type UIElicitationArrayEnumField struct { - Default []string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Items UIElicitationArrayEnumFieldItems `json:"items"` - MaxItems *float64 `json:"maxItems,omitempty"` - MinItems *float64 `json:"minItems,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationArrayEnumFieldType `json:"type"` +type MCPConfigUpdateResult struct { } -type UIElicitationArrayEnumFieldItems struct { - Enum []string `json:"enum"` - Type UIElicitationStringEnumFieldType `json:"type"` +type MCPDisableRequest struct { + // Name of the MCP server to disable + ServerName string `json:"serverName"` } -type UIElicitationArrayAnyOfField struct { - Default []string `json:"default,omitempty"` - Description *string `json:"description,omitempty"` - Items UIElicitationArrayAnyOfFieldItems `json:"items"` - MaxItems *float64 `json:"maxItems,omitempty"` - MinItems *float64 `json:"minItems,omitempty"` - Title *string `json:"title,omitempty"` - Type UIElicitationArrayEnumFieldType `json:"type"` +type MCPDisableResult struct { } -type UIElicitationArrayAnyOfFieldItems struct { - AnyOf []UIElicitationStringOneOfFieldOneOf `json:"anyOf"` +type MCPDiscoverRequest struct { + // Working directory used as context for discovery (e.g., plugin resolution) + WorkingDirectory *string `json:"workingDirectory,omitempty"` } -// The elicitation response (accept with form values, decline, or cancel) -type UIElicitationResponse struct { - // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) - Action UIElicitationResponseAction `json:"action"` - // The form values submitted by the user (present when action is 'accept') - Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` +type MCPDiscoverResult struct { + // MCP servers discovered from all sources + Servers []DiscoveredMCPServer `json:"servers"` } -type UIHandlePendingElicitationRequest struct { - // The unique request ID from the elicitation.requested event - RequestID string `json:"requestId"` - // The elicitation response (accept with form values, decline, or cancel) - Result UIElicitationResponse `json:"result"` +type MCPEnableRequest struct { + // Name of the MCP server to enable + ServerName string `json:"serverName"` } -type UIElicitationResult struct { - // Whether the response was accepted. False if the request was already resolved by another - // client. - Success bool `json:"success"` +type MCPEnableResult struct { } -type PermissionDecisionRequest struct { - // Request ID of the pending permission request - RequestID string `json:"requestId"` - Result PermissionDecision `json:"result"` +type MCPReloadResult struct { } -type PermissionDecision struct { - // The permission request was approved - // - // Denied because approval rules explicitly blocked it - // - // Denied because no approval rule matched and user confirmation was unavailable - // - // Denied by the user during an interactive prompt - // - // Denied by the organization's content exclusion policy - // - // Denied by a permission request hook registered by an extension or plugin - Kind Kind `json:"kind"` - // Rules that denied the request - Rules []any `json:"rules,omitempty"` - // Optional feedback from the user explaining the denial - Feedback *string `json:"feedback,omitempty"` - // Human-readable explanation of why the path was excluded - // - // Optional message from the hook explaining the denial - Message *string `json:"message,omitempty"` - // File path that triggered the exclusion - Path *string `json:"path,omitempty"` - // Whether to interrupt the current agent turn - Interrupt *bool `json:"interrupt,omitempty"` +type MCPServer 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 *MCPServerSource `json:"source,omitempty"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status MCPServerStatus `json:"status"` } -type PermissionRequestResult struct { - // Whether the permission request was handled successfully - Success bool `json:"success"` +type MCPServerConfigHTTP struct { + FilterMapping *FilterMapping `json:"filterMapping"` + Headers map[string]string `json:"headers,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + OauthClientID *string `json:"oauthClientId,omitempty"` + OauthPublicClient *bool `json:"oauthPublicClient,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + // Remote transport type. Defaults to "http" when omitted. + Type *MCPServerConfigHTTPType `json:"type,omitempty"` + URL string `json:"url"` } -// Describes a filesystem error. -type SessionFSError struct { - // Error classification - Code SessionFSErrorCode `json:"code"` - // Free-form detail about the error, for logging/diagnostics - Message *string `json:"message,omitempty"` +type MCPServerConfigLocal struct { + Args []string `json:"args"` + Command string `json:"command"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping *FilterMapping `json:"filterMapping"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + // Timeout in milliseconds for tool calls to this server. + Timeout *int64 `json:"timeout,omitempty"` + // Tools to include. Defaults to all tools if not specified. + Tools []string `json:"tools,omitempty"` + Type *MCPServerConfigLocalType `json:"type,omitempty"` } -type PingResult struct { - // Echoed message (or default greeting) - Message string `json:"message"` - // Server protocol version number - ProtocolVersion int64 `json:"protocolVersion"` - // Server timestamp in milliseconds - Timestamp int64 `json:"timestamp"` +type MCPServerList struct { + // Configured MCP servers + Servers []MCPServer `json:"servers"` } -type PingRequest struct { - // Optional message to echo back - Message *string `json:"message,omitempty"` +type ModeSetRequest struct { + // The agent mode. Valid values: "interactive", "plan", "autopilot". + Mode SessionMode `json:"mode"` } -type ModelList struct { - // List of available models with full metadata - Models []ModelElement `json:"models"` +type ModeSetResult struct { } type ModelElement struct { // Billing information Billing *ModelBilling `json:"billing,omitempty"` // Model capabilities and limits - Capabilities CapabilitiesClass `json:"capabilities"` + Capabilities ModelCapabilities `json:"capabilities"` // Default reasoning effort level (only present if model supports reasoning effort) DefaultReasoningEffort *string `json:"defaultReasoningEffort,omitempty"` // Model identifier (e.g., "claude-sonnet-4.5") @@ -344,15 +568,15 @@ type ModelBilling struct { } // Model capabilities and limits -type CapabilitiesClass struct { +type ModelCapabilities struct { // Token limits for prompts, outputs, and context window - Limits *CapabilitiesLimits `json:"limits,omitempty"` + Limits *ModelCapabilitiesLimits `json:"limits,omitempty"` // Feature flags indicating what the model supports Supports *ModelCapabilitiesSupports `json:"supports,omitempty"` } // Token limits for prompts, outputs, and context window -type CapabilitiesLimits struct { +type ModelCapabilitiesLimits struct { // Maximum total context window size in tokens MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` // Maximum number of output/completion tokens @@ -363,6 +587,24 @@ type CapabilitiesLimits struct { Vision *ModelCapabilitiesLimitsVision `json:"vision,omitempty"` } +// Vision-specific limits +type ModelCapabilitiesLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize int64 `json:"max_prompt_image_size"` + // Maximum number of images per prompt + MaxPromptImages int64 `json:"max_prompt_images"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types"` +} + +// Feature flags indicating what the model supports +type ModelCapabilitiesSupports struct { + // Whether this model supports reasoning effort configuration + ReasoningEffort *bool `json:"reasoningEffort,omitempty"` + // Whether this model supports vision/image input + Vision *bool `json:"vision,omitempty"` +} + // Policy state (if applicable) type ModelPolicy struct { // Current policy state for this model @@ -371,334 +613,420 @@ type ModelPolicy struct { Terms string `json:"terms"` } -type ToolList struct { - // List of available built-in tools with metadata - Tools []Tool `json:"tools"` +// Override individual model capabilities resolved by the runtime +type ModelCapabilitiesOverride struct { + // Token limits for prompts, outputs, and context window + Limits *ModelCapabilitiesOverrideLimits `json:"limits,omitempty"` + // Feature flags indicating what the model supports + Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` } -type Tool struct { - // Description of what the tool does - Description string `json:"description"` - // Optional instructions for how to use this tool effectively - Instructions *string `json:"instructions,omitempty"` - // Tool identifier (e.g., "bash", "grep", "str_replace_editor") - Name string `json:"name"` - // Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP - // tools) - NamespacedName *string `json:"namespacedName,omitempty"` - // JSON Schema for the tool's input parameters - Parameters map[string]any `json:"parameters,omitempty"` +// Token limits for prompts, outputs, and context window +type ModelCapabilitiesOverrideLimits struct { + // Maximum total context window size in tokens + MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` + MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` + MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` + Vision *ModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` } -type ToolsListRequest struct { - // Optional model ID — when provided, the returned tool list reflects model-specific - // overrides - Model *string `json:"model,omitempty"` +type ModelCapabilitiesOverrideLimitsVision struct { + // Maximum image size in bytes + MaxPromptImageSize *int64 `json:"max_prompt_image_size,omitempty"` + // Maximum number of images per prompt + MaxPromptImages *int64 `json:"max_prompt_images,omitempty"` + // MIME types the model accepts + SupportedMediaTypes []string `json:"supported_media_types,omitempty"` } -type AccountGetQuotaResult struct { - // Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) - QuotaSnapshots map[string]AccountQuotaSnapshot `json:"quotaSnapshots"` +// Feature flags indicating what the model supports +type ModelCapabilitiesOverrideSupports struct { + ReasoningEffort *bool `json:"reasoningEffort,omitempty"` + Vision *bool `json:"vision,omitempty"` } -type AccountQuotaSnapshot struct { - // Number of requests included in the entitlement - EntitlementRequests int64 `json:"entitlementRequests"` - // Number of overage requests made this period - Overage int64 `json:"overage"` - // Whether pay-per-request usage is allowed when quota is exhausted - OverageAllowedWithExhaustedQuota bool `json:"overageAllowedWithExhaustedQuota"` - // Percentage of entitlement remaining - RemainingPercentage float64 `json:"remainingPercentage"` - // Date when the quota resets (ISO 8601) - ResetDate *time.Time `json:"resetDate,omitempty"` - // Number of requests used so far this period - UsedRequests int64 `json:"usedRequests"` +type ModelList struct { + // List of available models with full metadata + Models []ModelElement `json:"models"` } -type MCPConfigList struct { - // All MCP servers from user config, keyed by name - Servers map[string]MCPServerConfig `json:"servers"` +type ModelSwitchToRequest struct { + // Override individual model capabilities resolved by the runtime + ModelCapabilities *ModelCapabilitiesOverride `json:"modelCapabilities,omitempty"` + // Model identifier to switch to + ModelID string `json:"modelId"` + // Reasoning effort level to use for the model + ReasoningEffort *string `json:"reasoningEffort,omitempty"` } -type MCPConfigAddResult struct { +type ModelSwitchToResult struct { + // Currently active model identifier after the switch + ModelID *string `json:"modelId,omitempty"` } -type MCPConfigAddRequest struct { - // MCP server configuration (local/stdio or remote/http) - Config MCPServerConfig `json:"config"` - // Unique name for the MCP server +type NameGetResult struct { + // The session name, falling back to the auto-generated summary, or null if neither exists + Name *string `json:"name"` +} + +type NameSetRequest struct { + // New session name (1–100 characters, trimmed of leading/trailing whitespace) Name string `json:"name"` } -type MCPConfigUpdateResult struct { +type NameSetResult struct { } -type MCPConfigUpdateRequest struct { - // MCP server configuration (local/stdio or remote/http) - Config MCPServerConfig `json:"config"` - // Name of the MCP server to update - Name string `json:"name"` +type PermissionDecision struct { + // The permission request was approved + // + // Denied because approval rules explicitly blocked it + // + // Denied because no approval rule matched and user confirmation was unavailable + // + // Denied by the user during an interactive prompt + // + // Denied by the organization's content exclusion policy + // + // Denied by a permission request hook registered by an extension or plugin + Kind PermissionDecisionKind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules,omitempty"` + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Human-readable explanation of why the path was excluded + // + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` + // File path that triggered the exclusion + Path *string `json:"path,omitempty"` + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` } -type MCPConfigRemoveResult struct { +type PermissionDecisionApproved struct { + // The permission request was approved + Kind PermissionDecisionApprovedKind `json:"kind"` } -type MCPConfigRemoveRequest struct { - // Name of the MCP server to remove - Name string `json:"name"` +type PermissionDecisionDeniedByContentExclusionPolicy struct { + // Denied by the organization's content exclusion policy + Kind PermissionDecisionDeniedByContentExclusionPolicyKind `json:"kind"` + // Human-readable explanation of why the path was excluded + Message string `json:"message"` + // File path that triggered the exclusion + Path string `json:"path"` } -type MCPDiscoverResult struct { - // MCP servers discovered from all sources - Servers []DiscoveredMCPServer `json:"servers"` +type PermissionDecisionDeniedByPermissionRequestHook struct { + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` + // Denied by a permission request hook registered by an extension or plugin + Kind PermissionDecisionDeniedByPermissionRequestHookKind `json:"kind"` + // Optional message from the hook explaining the denial + Message *string `json:"message,omitempty"` } -type MCPDiscoverRequest struct { - // Working directory used as context for discovery (e.g., plugin resolution) - WorkingDirectory *string `json:"workingDirectory,omitempty"` +type PermissionDecisionDeniedByRules struct { + // Denied because approval rules explicitly blocked it + Kind PermissionDecisionDeniedByRulesKind `json:"kind"` + // Rules that denied the request + Rules []any `json:"rules"` } -type SkillsConfigSetDisabledSkillsResult struct { +type PermissionDecisionDeniedInteractivelyByUser struct { + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Denied by the user during an interactive prompt + Kind PermissionDecisionDeniedInteractivelyByUserKind `json:"kind"` } -type SkillsConfigSetDisabledSkillsRequest struct { - // List of skill names to disable - DisabledSkills []string `json:"disabledSkills"` +type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser struct { + // Denied because no approval rule matched and user confirmation was unavailable + Kind PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind `json:"kind"` } -type SkillsDiscoverRequest struct { - // Optional list of project directory paths to scan for project-scoped skills - ProjectPaths []string `json:"projectPaths,omitempty"` - // Optional list of additional skill directory paths to include - SkillDirectories []string `json:"skillDirectories,omitempty"` +type PermissionDecisionRequest struct { + // Request ID of the pending permission request + RequestID string `json:"requestId"` + Result PermissionDecision `json:"result"` } -type SessionFSSetProviderResult struct { - // Whether the provider was set successfully +type PermissionRequestResult struct { + // Whether the permission request was handled successfully Success bool `json:"success"` } -type SessionFSSetProviderRequest struct { - // Path conventions used by this filesystem - Conventions SessionFSSetProviderConventions `json:"conventions"` - // Initial working directory for sessions - InitialCwd string `json:"initialCwd"` - // Path within each session's SessionFs where the runtime stores files for that session - SessionStatePath string `json:"sessionStatePath"` +type PingRequest struct { + // Optional message to echo back + Message *string `json:"message,omitempty"` } -// Experimental: SessionsForkResult is part of an experimental API and may change or be removed. -type SessionsForkResult struct { - // The new forked session's ID - SessionID string `json:"sessionId"` +type PingResult struct { + // Echoed message (or default greeting) + Message string `json:"message"` + // Server protocol version number + ProtocolVersion int64 `json:"protocolVersion"` + // Server timestamp in milliseconds + Timestamp int64 `json:"timestamp"` } -// Experimental: SessionsForkRequest is part of an experimental API and may change or be removed. -type SessionsForkRequest struct { - // Source session ID to fork from - SessionID string `json:"sessionId"` - // Optional event ID boundary. When provided, the fork includes only events before this ID - // (exclusive). When omitted, all events are included. - ToEventID *string `json:"toEventId,omitempty"` +type PlanDeleteResult struct { } -type ModelSwitchToResult struct { - // Currently active model identifier after the switch - ModelID *string `json:"modelId,omitempty"` +type PlanReadResult struct { + // The content of the plan file, or null if it does not exist + Content *string `json:"content"` + // Whether the plan file exists in the workspace + Exists bool `json:"exists"` + // Absolute file path of the plan file, or null if workspace is not enabled + Path *string `json:"path"` } -type ModelSwitchToRequest struct { - // Override individual model capabilities resolved by the runtime - ModelCapabilities *ModelCapabilitiesClass `json:"modelCapabilities,omitempty"` - // Model identifier to switch to - ModelID string `json:"modelId"` - // Reasoning effort level to use for the model - ReasoningEffort *string `json:"reasoningEffort,omitempty"` +type PlanUpdateRequest struct { + // The new content for the plan file + Content string `json:"content"` } -// Override individual model capabilities resolved by the runtime -type ModelCapabilitiesClass struct { - // Token limits for prompts, outputs, and context window - Limits *ModelCapabilitiesLimitsClass `json:"limits,omitempty"` - // Feature flags indicating what the model supports - Supports *ModelCapabilitiesOverrideSupports `json:"supports,omitempty"` +type PlanUpdateResult struct { } -// Token limits for prompts, outputs, and context window -type ModelCapabilitiesLimitsClass struct { - // Maximum total context window size in tokens - MaxContextWindowTokens *int64 `json:"max_context_window_tokens,omitempty"` - MaxOutputTokens *int64 `json:"max_output_tokens,omitempty"` - MaxPromptTokens *int64 `json:"max_prompt_tokens,omitempty"` - Vision *PurpleModelCapabilitiesOverrideLimitsVision `json:"vision,omitempty"` +type PluginElement struct { + // Whether the plugin is currently enabled + Enabled bool `json:"enabled"` + // Marketplace the plugin came from + Marketplace string `json:"marketplace"` + // Plugin name + Name string `json:"name"` + // Installed version + Version *string `json:"version,omitempty"` } -type ModeSetResult struct { +// Experimental: PluginList is part of an experimental API and may change or be removed. +type PluginList struct { + // Installed plugins + Plugins []PluginElement `json:"plugins"` } -type ModeSetRequest struct { - // The agent mode. Valid values: "interactive", "plan", "autopilot". - Mode SessionMode `json:"mode"` +type ServerSkill struct { + // Description of what the skill does + Description string `json:"description"` + // Whether the skill is currently enabled (based on global config) + Enabled bool `json:"enabled"` + // Unique identifier for the skill + Name string `json:"name"` + // Absolute path to the skill file + Path *string `json:"path,omitempty"` + // The project path this skill belongs to (only for project/inherited skills) + ProjectPath *string `json:"projectPath,omitempty"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source string `json:"source"` + // Whether the skill can be invoked by the user as a slash command + UserInvocable bool `json:"userInvocable"` } -type NameGetResult struct { - // The session name, falling back to the auto-generated summary, or null if neither exists - Name *string `json:"name"` +type ServerSkillList struct { + // All discovered skills across all sources + Skills []ServerSkill `json:"skills"` } -type NameSetResult struct { +type SessionFSAppendFileRequest struct { + // Content to append + Content string `json:"content"` + // Optional POSIX-style mode for newly created files + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type NameSetRequest struct { - // New session name (1–100 characters, trimmed of leading/trailing whitespace) - Name string `json:"name"` +// Describes a filesystem error. +type SessionFSError struct { + // Error classification + Code SessionFSErrorCode `json:"code"` + // Free-form detail about the error, for logging/diagnostics + Message *string `json:"message,omitempty"` } -type PlanReadResult struct { - // The content of the plan file, or null if it does not exist - Content *string `json:"content"` - // Whether the plan file exists in the workspace - Exists bool `json:"exists"` - // Absolute file path of the plan file, or null if workspace is not enabled - Path *string `json:"path"` +type SessionFSExistsRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type PlanUpdateResult struct { +type SessionFSExistsResult struct { + // Whether the path exists + Exists bool `json:"exists"` } -type PlanUpdateRequest struct { - // The new content for the plan file - Content string `json:"content"` +type SessionFSMkdirRequest struct { + // Optional POSIX-style mode for newly created directories + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Create parent directories as needed + Recursive *bool `json:"recursive,omitempty"` + // Target session identifier + SessionID string `json:"sessionId"` } -type PlanDeleteResult struct { +type SessionFSReadFileRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type WorkspacesGetWorkspaceResult struct { - // Current workspace metadata, or null if not available - Workspace *WorkspaceClass `json:"workspace"` +type SessionFSReadFileResult struct { + // File content as UTF-8 string + Content string `json:"content"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` } -type WorkspaceClass struct { - Branch *string `json:"branch,omitempty"` - ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` - CreatedAt *time.Time `json:"created_at,omitempty"` - Cwd *string `json:"cwd,omitempty"` - GitRoot *string `json:"git_root,omitempty"` - HostType *HostType `json:"host_type,omitempty"` - ID string `json:"id"` - McLastEventID *string `json:"mc_last_event_id,omitempty"` - McSessionID *string `json:"mc_session_id,omitempty"` - McTaskID *string `json:"mc_task_id,omitempty"` - Name *string `json:"name,omitempty"` - RemoteSteerable *bool `json:"remote_steerable,omitempty"` - Repository *string `json:"repository,omitempty"` - SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` - Summary *string `json:"summary,omitempty"` - SummaryCount *int64 `json:"summary_count,omitempty"` - UpdatedAt *time.Time `json:"updated_at,omitempty"` +type SessionFSReaddirRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type WorkspacesListFilesResult struct { - // Relative file paths in the workspace files directory - Files []string `json:"files"` +type SessionFSReaddirResult struct { + // Entry names in the directory + Entries []string `json:"entries"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` } -type WorkspacesReadFileResult struct { - // File content as a UTF-8 string - Content string `json:"content"` +type SessionFSReaddirWithTypesEntry struct { + // Entry name + Name string `json:"name"` + // Entry type + Type SessionFSReaddirWithTypesEntryType `json:"type"` } -type WorkspacesReadFileRequest struct { - // Relative path within the workspace files directory +type SessionFSReaddirWithTypesRequest struct { + // Path using SessionFs conventions Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -type WorkspacesCreateFileResult struct { +type SessionFSReaddirWithTypesResult struct { + // Directory entries with type information + Entries []SessionFSReaddirWithTypesEntry `json:"entries"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` +} + +type SessionFSRenameRequest struct { + // Destination path using SessionFs conventions + Dest string `json:"dest"` + // Target session identifier + SessionID string `json:"sessionId"` + // Source path using SessionFs conventions + Src string `json:"src"` } -type WorkspacesCreateFileRequest struct { - // File content to write as a UTF-8 string - Content string `json:"content"` - // Relative path within the workspace files directory +type SessionFSRmRequest struct { + // Ignore errors if the path does not exist + Force *bool `json:"force,omitempty"` + // Path using SessionFs conventions Path string `json:"path"` + // Remove directories and their contents recursively + Recursive *bool `json:"recursive,omitempty"` + // Target session identifier + SessionID string `json:"sessionId"` } -type InstructionsGetSourcesResult struct { - // Instruction sources for the session - Sources []InstructionsSources `json:"sources"` +type SessionFSSetProviderRequest struct { + // Path conventions used by this filesystem + Conventions SessionFSSetProviderConventions `json:"conventions"` + // Initial working directory for sessions + InitialCwd string `json:"initialCwd"` + // Path within each session's SessionFs where the runtime stores files for that session + SessionStatePath string `json:"sessionStatePath"` } -type InstructionsSources struct { - // Glob pattern from frontmatter — when set, this instruction applies only to matching files - ApplyTo *string `json:"applyTo,omitempty"` - // Raw content of the instruction file - Content string `json:"content"` - // Short description (body after frontmatter) for use in instruction tables - Description *string `json:"description,omitempty"` - // Unique identifier for this source (used for toggling) - ID string `json:"id"` - // Human-readable label - Label string `json:"label"` - // Where this source lives — used for UI grouping - Location InstructionsSourcesLocation `json:"location"` - // File path relative to repo or absolute for home - SourcePath string `json:"sourcePath"` - // Category of instruction source — used for merge logic - Type InstructionsSourcesType `json:"type"` +type SessionFSSetProviderResult struct { + // Whether the provider was set successfully + Success bool `json:"success"` } -// Experimental: FleetStartResult is part of an experimental API and may change or be removed. -type FleetStartResult struct { - // Whether fleet mode was successfully activated - Started bool `json:"started"` +type SessionFSStatRequest struct { + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -// Experimental: FleetStartRequest is part of an experimental API and may change or be removed. -type FleetStartRequest struct { - // Optional user prompt to combine with fleet instructions - Prompt *string `json:"prompt,omitempty"` +type SessionFSStatResult struct { + // ISO 8601 timestamp of creation + Birthtime time.Time `json:"birthtime"` + // Describes a filesystem error. + Error *SessionFSError `json:"error,omitempty"` + // Whether the path is a directory + IsDirectory bool `json:"isDirectory"` + // Whether the path is a file + IsFile bool `json:"isFile"` + // ISO 8601 timestamp of last modification + Mtime time.Time `json:"mtime"` + // File size in bytes + Size int64 `json:"size"` } -// Experimental: AgentList is part of an experimental API and may change or be removed. -type AgentList struct { - // Available custom agents - Agents []AgentInfo `json:"agents"` +type SessionFSWriteFileRequest struct { + // Content to write + Content string `json:"content"` + // Optional POSIX-style mode for newly created files + Mode *int64 `json:"mode,omitempty"` + // Path using SessionFs conventions + Path string `json:"path"` + // Target session identifier + SessionID string `json:"sessionId"` } -// Experimental: AgentGetCurrentResult is part of an experimental API and may change or be removed. -type AgentGetCurrentResult struct { - // Currently selected custom agent, or null if using the default agent - Agent *AgentInfo `json:"agent"` +// Experimental: SessionsForkRequest is part of an experimental API and may change or be removed. +type SessionsForkRequest struct { + // Source session ID to fork from + SessionID string `json:"sessionId"` + // Optional event ID boundary. When provided, the fork includes only events before this ID + // (exclusive). When omitted, all events are included. + ToEventID *string `json:"toEventId,omitempty"` } -// Experimental: AgentSelectResult is part of an experimental API and may change or be removed. -type AgentSelectResult struct { - // The newly selected custom agent - Agent AgentInfo `json:"agent"` +// Experimental: SessionsForkResult is part of an experimental API and may change or be removed. +type SessionsForkResult struct { + // The new forked session's ID + SessionID string `json:"sessionId"` } -// Experimental: AgentSelectRequest is part of an experimental API and may change or be removed. -type AgentSelectRequest struct { - // Name of the custom agent to select - Name string `json:"name"` +type ShellExecRequest struct { + // Shell command to execute + Command string `json:"command"` + // Working directory (defaults to session working directory) + Cwd *string `json:"cwd,omitempty"` + // Timeout in milliseconds (default: 30000) + Timeout *int64 `json:"timeout,omitempty"` } -// Experimental: AgentDeselectResult is part of an experimental API and may change or be removed. -type AgentDeselectResult struct { +type ShellExecResult struct { + // Unique identifier for tracking streamed output + ProcessID string `json:"processId"` } -// Experimental: AgentReloadResult is part of an experimental API and may change or be removed. -type AgentReloadResult struct { - // Reloaded custom agents - Agents []AgentInfo `json:"agents"` +type ShellKillRequest struct { + // Process identifier returned by shell.exec + ProcessID string `json:"processId"` + // Signal to send (default: SIGTERM) + Signal *ShellKillSignal `json:"signal,omitempty"` } -// Experimental: SkillList is part of an experimental API and may change or be removed. -type SkillList struct { - // Available skills - Skills []Skill `json:"skills"` +type ShellKillResult struct { + // Whether the signal was sent successfully + Killed bool `json:"killed"` } type Skill struct { @@ -716,18 +1044,18 @@ type Skill struct { UserInvocable bool `json:"userInvocable"` } -// Experimental: SkillsEnableResult is part of an experimental API and may change or be removed. -type SkillsEnableResult struct { +// Experimental: SkillList is part of an experimental API and may change or be removed. +type SkillList struct { + // Available skills + Skills []Skill `json:"skills"` } -// Experimental: SkillsEnableRequest is part of an experimental API and may change or be removed. -type SkillsEnableRequest struct { - // Name of the skill to enable - Name string `json:"name"` +type SkillsConfigSetDisabledSkillsRequest struct { + // List of skill names to disable + DisabledSkills []string `json:"disabledSkills"` } -// Experimental: SkillsDisableResult is part of an experimental API and may change or be removed. -type SkillsDisableResult struct { +type SkillsConfigSetDisabledSkillsResult struct { } // Experimental: SkillsDisableRequest is part of an experimental API and may change or be removed. @@ -736,87 +1064,59 @@ type SkillsDisableRequest struct { Name string `json:"name"` } -// Experimental: SkillsReloadResult is part of an experimental API and may change or be removed. -type SkillsReloadResult struct { -} - -type MCPEnableResult struct { -} - -type MCPEnableRequest struct { - // Name of the MCP server to enable - ServerName string `json:"serverName"` -} - -type MCPDisableResult struct { -} - -type MCPDisableRequest struct { - // Name of the MCP server to disable - ServerName string `json:"serverName"` -} - -type MCPReloadResult struct { -} - -// Experimental: PluginList is part of an experimental API and may change or be removed. -type PluginList struct { - // Installed plugins - Plugins []PluginElement `json:"plugins"` -} - -type PluginElement struct { - // Whether the plugin is currently enabled - Enabled bool `json:"enabled"` - // Marketplace the plugin came from - Marketplace string `json:"marketplace"` - // Plugin name - Name string `json:"name"` - // Installed version - Version *string `json:"version,omitempty"` +// Experimental: SkillsDisableResult is part of an experimental API and may change or be removed. +type SkillsDisableResult struct { } -// Experimental: ExtensionList is part of an experimental API and may change or be removed. -type ExtensionList struct { - // Discovered extensions and their current status - Extensions []Extension `json:"extensions"` +type SkillsDiscoverRequest struct { + // Optional list of project directory paths to scan for project-scoped skills + ProjectPaths []string `json:"projectPaths,omitempty"` + // Optional list of additional skill directory paths to include + SkillDirectories []string `json:"skillDirectories,omitempty"` } -type Extension struct { - // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') - ID string `json:"id"` - // Extension name (directory name) +// Experimental: SkillsEnableRequest is part of an experimental API and may change or be removed. +type SkillsEnableRequest struct { + // Name of the skill to enable Name string `json:"name"` - // Process ID if the extension is running - PID *int64 `json:"pid,omitempty"` - // Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) - Source ExtensionSource `json:"source"` - // Current status: running, disabled, failed, or starting - Status ExtensionStatus `json:"status"` } -// Experimental: ExtensionsEnableResult is part of an experimental API and may change or be removed. -type ExtensionsEnableResult struct { +// Experimental: SkillsEnableResult is part of an experimental API and may change or be removed. +type SkillsEnableResult struct { } -// Experimental: ExtensionsEnableRequest is part of an experimental API and may change or be removed. -type ExtensionsEnableRequest struct { - // Source-qualified extension ID to enable - ID string `json:"id"` +// Experimental: SkillsReloadResult is part of an experimental API and may change or be removed. +type SkillsReloadResult struct { } -// Experimental: ExtensionsDisableResult is part of an experimental API and may change or be removed. -type ExtensionsDisableResult struct { +type Tool struct { + // Description of what the tool does + Description string `json:"description"` + // Optional instructions for how to use this tool effectively + Instructions *string `json:"instructions,omitempty"` + // Tool identifier (e.g., "bash", "grep", "str_replace_editor") + Name string `json:"name"` + // Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP + // tools) + NamespacedName *string `json:"namespacedName,omitempty"` + // JSON Schema for the tool's input parameters + Parameters map[string]any `json:"parameters,omitempty"` } -// Experimental: ExtensionsDisableRequest is part of an experimental API and may change or be removed. -type ExtensionsDisableRequest struct { - // Source-qualified extension ID to disable - ID string `json:"id"` +type ToolCallResult struct { + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Type of the tool result + ResultType *string `json:"resultType,omitempty"` + // Text result to send back to the LLM + TextResultForLlm string `json:"textResultForLlm"` + // Telemetry data from tool execution + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } - -// Experimental: ExtensionsReloadResult is part of an experimental API and may change or be removed. -type ExtensionsReloadResult struct { + +type ToolList struct { + // List of available built-in tools with metadata + Tools []Tool `json:"tools"` } type ToolsHandlePendingToolCallRequest struct { @@ -828,16 +1128,44 @@ type ToolsHandlePendingToolCallRequest struct { Result *ToolsHandlePendingToolCall `json:"result"` } -type CommandsHandlePendingCommandResult struct { - // Whether the command was handled successfully - Success bool `json:"success"` +type ToolsListRequest struct { + // Optional model ID — when provided, the returned tool list reflects model-specific + // overrides + Model *string `json:"model,omitempty"` } -type CommandsHandlePendingCommandRequest struct { - // Error message if the command handler failed - Error *string `json:"error,omitempty"` - // Request ID from the command invocation event - RequestID string `json:"requestId"` +type UIElicitationArrayAnyOfField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayAnyOfFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayAnyOfFieldType `json:"type"` +} + +type UIElicitationArrayAnyOfFieldItems struct { + AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf"` +} + +type UIElicitationArrayAnyOfFieldItemsAnyOf struct { + Const string `json:"const"` + Title string `json:"title"` +} + +type UIElicitationArrayEnumField struct { + Default []string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Items UIElicitationArrayEnumFieldItems `json:"items"` + MaxItems *float64 `json:"maxItems,omitempty"` + MinItems *float64 `json:"minItems,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayAnyOfFieldType `json:"type"` +} + +type UIElicitationArrayEnumFieldItems struct { + Enum []string `json:"enum"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } type UIElicitationRequest struct { @@ -854,7 +1182,7 @@ type UIElicitationSchema struct { // List of required field names Required []string `json:"required,omitempty"` // Schema type indicator (always 'object') - Type RequestedSchemaType `json:"type"` + Type UIElicitationSchemaType `json:"type"` } type UIElicitationSchemaProperty struct { @@ -863,7 +1191,7 @@ type UIElicitationSchemaProperty struct { Enum []string `json:"enum,omitempty"` EnumNames []string `json:"enumNames,omitempty"` Title *string `json:"title,omitempty"` - Type UIElicitationSchemaPropertyNumberType `json:"type"` + Type UIElicitationSchemaPropertyType `json:"type"` OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf,omitempty"` Items *UIElicitationArrayFieldItems `json:"items,omitempty"` MaxItems *float64 `json:"maxItems,omitempty"` @@ -876,92 +1204,78 @@ type UIElicitationSchemaProperty struct { } type UIElicitationArrayFieldItems struct { - Enum []string `json:"enum,omitempty"` - Type *UIElicitationStringEnumFieldType `json:"type,omitempty"` - AnyOf []UIElicitationStringOneOfFieldOneOf `json:"anyOf,omitempty"` -} - -type LogResult struct { - // The unique identifier of the emitted session event - EventID string `json:"eventId"` + Enum []string `json:"enum,omitempty"` + Type *UIElicitationArrayEnumFieldItemsType `json:"type,omitempty"` + AnyOf []UIElicitationArrayAnyOfFieldItemsAnyOf `json:"anyOf,omitempty"` } -type LogRequest struct { - // When true, the message is transient and not persisted to the session event log on disk - Ephemeral *bool `json:"ephemeral,omitempty"` - // Log severity level. Determines how the message is displayed in the timeline. Defaults to - // "info". - Level *SessionLogLevel `json:"level,omitempty"` - // Human-readable message - Message string `json:"message"` - // Optional URL the user can open in their browser for more details - URL *string `json:"url,omitempty"` +type UIElicitationStringOneOfFieldOneOf struct { + Const string `json:"const"` + Title string `json:"title"` } -type ShellExecResult struct { - // Unique identifier for tracking streamed output - ProcessID string `json:"processId"` +// The elicitation response (accept with form values, decline, or cancel) +type UIElicitationResponse struct { + // The user's response: accept (submitted), decline (rejected), or cancel (dismissed) + Action UIElicitationResponseAction `json:"action"` + // The form values submitted by the user (present when action is 'accept') + Content map[string]*UIElicitationFieldValue `json:"content,omitempty"` } -type ShellExecRequest struct { - // Shell command to execute - Command string `json:"command"` - // Working directory (defaults to session working directory) - Cwd *string `json:"cwd,omitempty"` - // Timeout in milliseconds (default: 30000) - Timeout *int64 `json:"timeout,omitempty"` +type UIElicitationResult struct { + // Whether the response was accepted. False if the request was already resolved by another + // client. + Success bool `json:"success"` } -type ShellKillResult struct { - // Whether the signal was sent successfully - Killed bool `json:"killed"` +type UIElicitationSchemaPropertyBoolean struct { + Default *bool `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationSchemaPropertyBooleanType `json:"type"` } -type ShellKillRequest struct { - // Process identifier returned by shell.exec - ProcessID string `json:"processId"` - // Signal to send (default: SIGTERM) - Signal *ShellKillSignal `json:"signal,omitempty"` +type UIElicitationSchemaPropertyNumber struct { + Default *float64 `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Maximum *float64 `json:"maximum,omitempty"` + Minimum *float64 `json:"minimum,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationSchemaPropertyNumberType `json:"type"` } -// Experimental: HistoryCompactResult is part of an experimental API and may change or be removed. -type HistoryCompactResult struct { - // Post-compaction context window usage breakdown - ContextWindow *HistoryCompactContextWindow `json:"contextWindow,omitempty"` - // Number of messages removed during compaction - MessagesRemoved int64 `json:"messagesRemoved"` - // Whether compaction completed successfully - Success bool `json:"success"` - // Number of tokens freed by compaction - TokensRemoved int64 `json:"tokensRemoved"` +type UIElicitationSchemaPropertyString struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Format *UIElicitationSchemaPropertyStringFormat `json:"format,omitempty"` + MaxLength *float64 `json:"maxLength,omitempty"` + MinLength *float64 `json:"minLength,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Post-compaction context window usage breakdown -type HistoryCompactContextWindow struct { - // Token count from non-system messages (user, assistant, tool) - ConversationTokens *int64 `json:"conversationTokens,omitempty"` - // Current total tokens in the context window (system + conversation + tool definitions) - CurrentTokens int64 `json:"currentTokens"` - // Current number of messages in the conversation - MessagesLength int64 `json:"messagesLength"` - // Token count from system message(s) - SystemTokens *int64 `json:"systemTokens,omitempty"` - // Maximum token count for the model's context window - TokenLimit int64 `json:"tokenLimit"` - // Token count from tool definitions - ToolDefinitionsTokens *int64 `json:"toolDefinitionsTokens,omitempty"` +type UIElicitationStringEnumField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + Enum []string `json:"enum"` + EnumNames []string `json:"enumNames,omitempty"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Experimental: HistoryTruncateResult is part of an experimental API and may change or be removed. -type HistoryTruncateResult struct { - // Number of events that were removed - EventsRemoved int64 `json:"eventsRemoved"` +type UIElicitationStringOneOfField struct { + Default *string `json:"default,omitempty"` + Description *string `json:"description,omitempty"` + OneOf []UIElicitationStringOneOfFieldOneOf `json:"oneOf"` + Title *string `json:"title,omitempty"` + Type UIElicitationArrayEnumFieldItemsType `json:"type"` } -// Experimental: HistoryTruncateRequest is part of an experimental API and may change or be removed. -type HistoryTruncateRequest struct { - // Event ID to truncate to. This event and all events after it are removed from the session. - EventID string `json:"eventId"` +type UIHandlePendingElicitationRequest struct { + // The unique request ID from the elicitation.requested event + RequestID string `json:"requestId"` + // The elicitation response (accept with form values, decline, or cancel) + Result UIElicitationResponse `json:"result"` } // Experimental: UsageGetMetricsResult is part of an experimental API and may change or be removed. @@ -1026,160 +1340,56 @@ type UsageMetricsModelMetricUsage struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } -type SessionFSReadFileResult struct { - // File content as UTF-8 string - Content string `json:"content"` - // Describes a filesystem error. - Error *SessionFSError `json:"error,omitempty"` -} - -type SessionFSReadFileRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSWriteFileRequest struct { - // Content to write - Content string `json:"content"` - // Optional POSIX-style mode for newly created files - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSAppendFileRequest struct { - // Content to append +type WorkspacesCreateFileRequest struct { + // File content to write as a UTF-8 string Content string `json:"content"` - // Optional POSIX-style mode for newly created files - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSExistsResult struct { - // Whether the path exists - Exists bool `json:"exists"` -} - -type SessionFSExistsRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSStatResult struct { - // ISO 8601 timestamp of creation - Birthtime time.Time `json:"birthtime"` - // Describes a filesystem error. - Error *SessionFSError `json:"error,omitempty"` - // Whether the path is a directory - IsDirectory bool `json:"isDirectory"` - // Whether the path is a file - IsFile bool `json:"isFile"` - // ISO 8601 timestamp of last modification - Mtime time.Time `json:"mtime"` - // File size in bytes - Size int64 `json:"size"` -} - -type SessionFSStatRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSMkdirRequest struct { - // Optional POSIX-style mode for newly created directories - Mode *int64 `json:"mode,omitempty"` - // Path using SessionFs conventions + // Relative path within the workspace files directory Path string `json:"path"` - // Create parent directories as needed - Recursive *bool `json:"recursive,omitempty"` - // Target session identifier - SessionID string `json:"sessionId"` -} - -type SessionFSReaddirResult struct { - // Entry names in the directory - Entries []string `json:"entries"` - // Describes a filesystem error. - Error *SessionFSError `json:"error,omitempty"` } -type SessionFSReaddirRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` +type WorkspacesCreateFileResult struct { } -type SessionFSReaddirWithTypesResult struct { - // Directory entries with type information - Entries []SessionFSReaddirWithTypesEntry `json:"entries"` - // Describes a filesystem error. - Error *SessionFSError `json:"error,omitempty"` +type WorkspacesGetWorkspaceResult struct { + // Current workspace metadata, or null if not available + Workspace *WorkspaceClass `json:"workspace"` } -type SessionFSReaddirWithTypesEntry struct { - // Entry name - Name string `json:"name"` - // Entry type - Type SessionFSReaddirWithTypesEntryType `json:"type"` +type WorkspaceClass struct { + Branch *string `json:"branch,omitempty"` + ChronicleSyncDismissed *bool `json:"chronicle_sync_dismissed,omitempty"` + CreatedAt *time.Time `json:"created_at,omitempty"` + Cwd *string `json:"cwd,omitempty"` + GitRoot *string `json:"git_root,omitempty"` + HostType *HostType `json:"host_type,omitempty"` + ID string `json:"id"` + McLastEventID *string `json:"mc_last_event_id,omitempty"` + McSessionID *string `json:"mc_session_id,omitempty"` + McTaskID *string `json:"mc_task_id,omitempty"` + Name *string `json:"name,omitempty"` + RemoteSteerable *bool `json:"remote_steerable,omitempty"` + Repository *string `json:"repository,omitempty"` + SessionSyncLevel *SessionSyncLevel `json:"session_sync_level,omitempty"` + Summary *string `json:"summary,omitempty"` + SummaryCount *int64 `json:"summary_count,omitempty"` + UpdatedAt *time.Time `json:"updated_at,omitempty"` } -type SessionFSReaddirWithTypesRequest struct { - // Path using SessionFs conventions - Path string `json:"path"` - // Target session identifier - SessionID string `json:"sessionId"` +type WorkspacesListFilesResult struct { + // Relative file paths in the workspace files directory + Files []string `json:"files"` } -type SessionFSRmRequest struct { - // Ignore errors if the path does not exist - Force *bool `json:"force,omitempty"` - // Path using SessionFs conventions +type WorkspacesReadFileRequest struct { + // Relative path within the workspace files directory Path string `json:"path"` - // Remove directories and their contents recursively - Recursive *bool `json:"recursive,omitempty"` - // Target session identifier - SessionID string `json:"sessionId"` } -type SessionFSRenameRequest struct { - // Destination path using SessionFs conventions - Dest string `json:"dest"` - // Target session identifier - SessionID string `json:"sessionId"` - // Source path using SessionFs conventions - Src string `json:"src"` +type WorkspacesReadFileResult struct { + // File content as a UTF-8 string + Content string `json:"content"` } -type FilterMappingString string - -const ( - FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" - FilterMappingStringMarkdown FilterMappingString = "markdown" - FilterMappingStringNone FilterMappingString = "none" -) - -// Remote transport type. Defaults to "http" when omitted. -type MCPServerConfigType string - -const ( - MCPServerConfigTypeHTTP MCPServerConfigType = "http" - MCPServerConfigTypeLocal MCPServerConfigType = "local" - MCPServerConfigTypeSSE MCPServerConfigType = "sse" - MCPServerConfigTypeStdio MCPServerConfigType = "stdio" -) - // Configuration source // // Configuration source: user, workspace, plugin, or builtin @@ -1202,6 +1412,73 @@ const ( DiscoveredMCPServerTypeMemory DiscoveredMCPServerType = "memory" ) +// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) +type ExtensionSource string + +const ( + ExtensionSourceUser ExtensionSource = "user" + ExtensionSourceProject ExtensionSource = "project" +) + +// Current status: running, disabled, failed, or starting +type ExtensionStatus string + +const ( + ExtensionStatusDisabled ExtensionStatus = "disabled" + ExtensionStatusFailed ExtensionStatus = "failed" + ExtensionStatusRunning ExtensionStatus = "running" + ExtensionStatusStarting ExtensionStatus = "starting" +) + +type FilterMappingString string + +const ( + FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" + FilterMappingStringMarkdown FilterMappingString = "markdown" + FilterMappingStringNone FilterMappingString = "none" +) + +// Where this source lives — used for UI grouping +type InstructionsSourcesLocation string + +const ( + InstructionsSourcesLocationUser InstructionsSourcesLocation = "user" + InstructionsSourcesLocationRepository InstructionsSourcesLocation = "repository" + InstructionsSourcesLocationWorkingDirectory InstructionsSourcesLocation = "working-directory" +) + +// Category of instruction source — used for merge logic +type InstructionsSourcesType string + +const ( + InstructionsSourcesTypeChildInstructions InstructionsSourcesType = "child-instructions" + InstructionsSourcesTypeHome InstructionsSourcesType = "home" + InstructionsSourcesTypeModel InstructionsSourcesType = "model" + InstructionsSourcesTypeNestedAgents InstructionsSourcesType = "nested-agents" + InstructionsSourcesTypeRepo InstructionsSourcesType = "repo" + InstructionsSourcesTypeVscode InstructionsSourcesType = "vscode" +) + +// Log severity level. Determines how the message is displayed in the timeline. Defaults to +// "info". +type SessionLogLevel string + +const ( + SessionLogLevelError SessionLogLevel = "error" + SessionLogLevelInfo SessionLogLevel = "info" + SessionLogLevelWarning SessionLogLevel = "warning" +) + +// Remote transport type. Defaults to "http" when omitted. +type MCPServerConfigType string + +const ( + MCPServerConfigTypeHTTP MCPServerConfigType = "http" + MCPServerConfigTypeLocal MCPServerConfigType = "local" + MCPServerConfigTypeSSE MCPServerConfigType = "sse" + MCPServerConfigTypeStdio MCPServerConfigType = "stdio" +) + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured type MCPServerStatus string @@ -1214,115 +1491,120 @@ const ( MCPServerStatusPending MCPServerStatus = "pending" ) -type UIElicitationStringEnumFieldType string +// Remote transport type. Defaults to "http" when omitted. +type MCPServerConfigHTTPType string const ( - UIElicitationStringEnumFieldTypeString UIElicitationStringEnumFieldType = "string" + MCPServerConfigHTTPTypeHTTP MCPServerConfigHTTPType = "http" + MCPServerConfigHTTPTypeSSE MCPServerConfigHTTPType = "sse" ) -type UIElicitationArrayEnumFieldType string +type MCPServerConfigLocalType string const ( - UIElicitationArrayEnumFieldTypeArray UIElicitationArrayEnumFieldType = "array" + MCPServerConfigLocalTypeLocal MCPServerConfigLocalType = "local" + MCPServerConfigLocalTypeStdio MCPServerConfigLocalType = "stdio" ) -// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) -type UIElicitationResponseAction string +// The agent mode. Valid values: "interactive", "plan", "autopilot". +type SessionMode string const ( - UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" - UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" - UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" + SessionModeAutopilot SessionMode = "autopilot" + SessionModeInteractive SessionMode = "interactive" + SessionModePlan SessionMode = "plan" ) -type Kind string +type PermissionDecisionKind string const ( - KindApproved Kind = "approved" - KindDeniedByContentExclusionPolicy Kind = "denied-by-content-exclusion-policy" - KindDeniedByPermissionRequestHook Kind = "denied-by-permission-request-hook" - KindDeniedByRules Kind = "denied-by-rules" - KindDeniedInteractivelyByUser Kind = "denied-interactively-by-user" - KindDeniedNoApprovalRuleAndCouldNotRequestFromUser Kind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionDecisionKindApproved PermissionDecisionKind = "approved" + PermissionDecisionKindDeniedByContentExclusionPolicy PermissionDecisionKind = "denied-by-content-exclusion-policy" + PermissionDecisionKindDeniedByPermissionRequestHook PermissionDecisionKind = "denied-by-permission-request-hook" + PermissionDecisionKindDeniedByRules PermissionDecisionKind = "denied-by-rules" + PermissionDecisionKindDeniedInteractivelyByUser PermissionDecisionKind = "denied-interactively-by-user" + PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionKind = "denied-no-approval-rule-and-could-not-request-from-user" ) -// Error classification -type SessionFSErrorCode string +type PermissionDecisionApprovedKind string const ( - SessionFSErrorCodeENOENT SessionFSErrorCode = "ENOENT" - SessionFSErrorCodeUNKNOWN SessionFSErrorCode = "UNKNOWN" + PermissionDecisionApprovedKindApproved PermissionDecisionApprovedKind = "approved" ) -// Path conventions used by this filesystem -type SessionFSSetProviderConventions string +type PermissionDecisionDeniedByContentExclusionPolicyKind string const ( - SessionFSSetProviderConventionsPosix SessionFSSetProviderConventions = "posix" - SessionFSSetProviderConventionsWindows SessionFSSetProviderConventions = "windows" + PermissionDecisionDeniedByContentExclusionPolicyKindDeniedByContentExclusionPolicy PermissionDecisionDeniedByContentExclusionPolicyKind = "denied-by-content-exclusion-policy" ) -// The agent mode. Valid values: "interactive", "plan", "autopilot". -type SessionMode string +type PermissionDecisionDeniedByPermissionRequestHookKind string const ( - SessionModeAutopilot SessionMode = "autopilot" - SessionModeInteractive SessionMode = "interactive" - SessionModePlan SessionMode = "plan" + PermissionDecisionDeniedByPermissionRequestHookKindDeniedByPermissionRequestHook PermissionDecisionDeniedByPermissionRequestHookKind = "denied-by-permission-request-hook" ) -type HostType string +type PermissionDecisionDeniedByRulesKind string const ( - HostTypeAdo HostType = "ado" - HostTypeGithub HostType = "github" + PermissionDecisionDeniedByRulesKindDeniedByRules PermissionDecisionDeniedByRulesKind = "denied-by-rules" ) -type SessionSyncLevel string +type PermissionDecisionDeniedInteractivelyByUserKind string const ( - SessionSyncLevelRepoAndUser SessionSyncLevel = "repo_and_user" - SessionSyncLevelLocal SessionSyncLevel = "local" - SessionSyncLevelUser SessionSyncLevel = "user" + PermissionDecisionDeniedInteractivelyByUserKindDeniedInteractivelyByUser PermissionDecisionDeniedInteractivelyByUserKind = "denied-interactively-by-user" ) -// Where this source lives — used for UI grouping -type InstructionsSourcesLocation string +type PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind string const ( - InstructionsSourcesLocationUser InstructionsSourcesLocation = "user" - InstructionsSourcesLocationRepository InstructionsSourcesLocation = "repository" - InstructionsSourcesLocationWorkingDirectory InstructionsSourcesLocation = "working-directory" + PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind = "denied-no-approval-rule-and-could-not-request-from-user" ) -// Category of instruction source — used for merge logic -type InstructionsSourcesType string +// Error classification +type SessionFSErrorCode string const ( - InstructionsSourcesTypeChildInstructions InstructionsSourcesType = "child-instructions" - InstructionsSourcesTypeHome InstructionsSourcesType = "home" - InstructionsSourcesTypeModel InstructionsSourcesType = "model" - InstructionsSourcesTypeNestedAgents InstructionsSourcesType = "nested-agents" - InstructionsSourcesTypeRepo InstructionsSourcesType = "repo" - InstructionsSourcesTypeVscode InstructionsSourcesType = "vscode" + SessionFSErrorCodeENOENT SessionFSErrorCode = "ENOENT" + SessionFSErrorCodeUNKNOWN SessionFSErrorCode = "UNKNOWN" ) -// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) -type ExtensionSource string +// Entry type +type SessionFSReaddirWithTypesEntryType string const ( - ExtensionSourceUser ExtensionSource = "user" - ExtensionSourceProject ExtensionSource = "project" + SessionFSReaddirWithTypesEntryTypeDirectory SessionFSReaddirWithTypesEntryType = "directory" + SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" ) -// Current status: running, disabled, failed, or starting -type ExtensionStatus string +// Path conventions used by this filesystem +type SessionFSSetProviderConventions string const ( - ExtensionStatusDisabled ExtensionStatus = "disabled" - ExtensionStatusFailed ExtensionStatus = "failed" - ExtensionStatusRunning ExtensionStatus = "running" - ExtensionStatusStarting ExtensionStatus = "starting" + SessionFSSetProviderConventionsPosix SessionFSSetProviderConventions = "posix" + SessionFSSetProviderConventionsWindows SessionFSSetProviderConventions = "windows" +) + +// Signal to send (default: SIGTERM) +type ShellKillSignal string + +const ( + ShellKillSignalSIGINT ShellKillSignal = "SIGINT" + ShellKillSignalSIGKILL ShellKillSignal = "SIGKILL" + ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" +) + +type UIElicitationArrayAnyOfFieldType string + +const ( + UIElicitationArrayAnyOfFieldTypeArray UIElicitationArrayAnyOfFieldType = "array" +) + +type UIElicitationArrayEnumFieldItemsType string + +const ( + UIElicitationArrayEnumFieldItemsTypeString UIElicitationArrayEnumFieldItemsType = "string" ) type UIElicitationSchemaPropertyStringFormat string @@ -1334,47 +1616,57 @@ const ( UIElicitationSchemaPropertyStringFormatURI UIElicitationSchemaPropertyStringFormat = "uri" ) -type UIElicitationSchemaPropertyNumberType string +type UIElicitationSchemaPropertyType string const ( - UIElicitationSchemaPropertyNumberTypeBoolean UIElicitationSchemaPropertyNumberType = "boolean" - UIElicitationSchemaPropertyNumberTypeInteger UIElicitationSchemaPropertyNumberType = "integer" - UIElicitationSchemaPropertyNumberTypeNumber UIElicitationSchemaPropertyNumberType = "number" - UIElicitationSchemaPropertyNumberTypeArray UIElicitationSchemaPropertyNumberType = "array" - UIElicitationSchemaPropertyNumberTypeString UIElicitationSchemaPropertyNumberType = "string" + UIElicitationSchemaPropertyTypeInteger UIElicitationSchemaPropertyType = "integer" + UIElicitationSchemaPropertyTypeNumber UIElicitationSchemaPropertyType = "number" + UIElicitationSchemaPropertyTypeArray UIElicitationSchemaPropertyType = "array" + UIElicitationSchemaPropertyTypeBoolean UIElicitationSchemaPropertyType = "boolean" + UIElicitationSchemaPropertyTypeString UIElicitationSchemaPropertyType = "string" ) -type RequestedSchemaType string +type UIElicitationSchemaType string const ( - RequestedSchemaTypeObject RequestedSchemaType = "object" + UIElicitationSchemaTypeObject UIElicitationSchemaType = "object" ) -// Log severity level. Determines how the message is displayed in the timeline. Defaults to -// "info". -type SessionLogLevel string +// The user's response: accept (submitted), decline (rejected), or cancel (dismissed) +type UIElicitationResponseAction string const ( - SessionLogLevelError SessionLogLevel = "error" - SessionLogLevelInfo SessionLogLevel = "info" - SessionLogLevelWarning SessionLogLevel = "warning" + UIElicitationResponseActionAccept UIElicitationResponseAction = "accept" + UIElicitationResponseActionCancel UIElicitationResponseAction = "cancel" + UIElicitationResponseActionDecline UIElicitationResponseAction = "decline" ) -// Signal to send (default: SIGTERM) -type ShellKillSignal string +type UIElicitationSchemaPropertyBooleanType string const ( - ShellKillSignalSIGINT ShellKillSignal = "SIGINT" - ShellKillSignalSIGKILL ShellKillSignal = "SIGKILL" - ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" + UIElicitationSchemaPropertyBooleanTypeBoolean UIElicitationSchemaPropertyBooleanType = "boolean" ) -// Entry type -type SessionFSReaddirWithTypesEntryType string +type UIElicitationSchemaPropertyNumberType string const ( - SessionFSReaddirWithTypesEntryTypeDirectory SessionFSReaddirWithTypesEntryType = "directory" - SessionFSReaddirWithTypesEntryTypeFile SessionFSReaddirWithTypesEntryType = "file" + UIElicitationSchemaPropertyNumberTypeInteger UIElicitationSchemaPropertyNumberType = "integer" + UIElicitationSchemaPropertyNumberTypeNumber UIElicitationSchemaPropertyNumberType = "number" +) + +type HostType string + +const ( + HostTypeAdo HostType = "ado" + HostTypeGithub HostType = "github" +) + +type SessionSyncLevel string + +const ( + SessionSyncLevelRepoAndUser SessionSyncLevel = "repo_and_user" + SessionSyncLevelLocal SessionSyncLevel = "local" + SessionSyncLevelUser SessionSyncLevel = "user" ) type FilterMapping struct { @@ -1382,6 +1674,12 @@ type FilterMapping struct { EnumMap map[string]FilterMappingString } +// Tool call result (string or expanded result object) +type ToolsHandlePendingToolCall struct { + String *string + ToolCallResult *ToolCallResult +} + type UIElicitationFieldValue struct { Bool *bool Double *float64 @@ -1389,12 +1687,6 @@ type UIElicitationFieldValue struct { StringArray []string } -// Tool call result (string or expanded result object) -type ToolsHandlePendingToolCall struct { - String *string - ToolCallResult *ToolCallResult -} - type serverApi struct { client *jsonrpc2.Client } diff --git a/go/session.go b/go/session.go index f40108aaf..99256856d 100644 --- a/go/session.go +++ b/go/session.go @@ -704,10 +704,10 @@ func (ui *SessionUI) Confirm(ctx context.Context, message string) (bool, error) rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "confirmed": { - Type: rpc.UIElicitationSchemaPropertyNumberTypeBoolean, + Type: rpc.UIElicitationSchemaPropertyTypeBoolean, Default: defaultTrue, }, }, @@ -734,10 +734,10 @@ func (ui *SessionUI) Select(ctx context.Context, message string, options []strin rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "selection": { - Type: rpc.UIElicitationSchemaPropertyNumberTypeString, + Type: rpc.UIElicitationSchemaPropertyTypeString, Enum: options, }, }, @@ -761,7 +761,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio if err := ui.session.assertElicitation(); err != nil { return "", false, err } - prop := rpc.UIElicitationSchemaProperty{Type: rpc.UIElicitationSchemaPropertyNumberTypeString} + prop := rpc.UIElicitationSchemaProperty{Type: rpc.UIElicitationSchemaPropertyTypeString} if opts != nil { if opts.Title != "" { prop.Title = &opts.Title @@ -788,7 +788,7 @@ func (ui *SessionUI) Input(ctx context.Context, message string, opts *InputOptio rpcResult, err := ui.session.RPC.UI.Elicitation(ctx, &rpc.UIElicitationRequest{ Message: message, RequestedSchema: rpc.UIElicitationSchema{ - Type: rpc.RequestedSchemaTypeObject, + Type: rpc.UIElicitationSchemaTypeObject, Properties: map[string]rpc.UIElicitationSchemaProperty{ "value": prop, }, @@ -1029,7 +1029,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) } @@ -1044,7 +1044,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.KindDeniedNoApprovalRuleAndCouldNotRequestFromUser, + Kind: rpc.PermissionDecisionKindDeniedNoApprovalRuleAndCouldNotRequestFromUser, }, }) return @@ -1056,7 +1056,7 @@ func (s *Session) executePermissionAndRespond(requestID string, permissionReques s.RPC.Permissions.HandlePendingPermissionRequest(context.Background(), &rpc.PermissionDecisionRequest{ RequestID: requestID, Result: rpc.PermissionDecision{ - Kind: rpc.Kind(result.Kind), + Kind: rpc.PermissionDecisionKind(result.Kind), Rules: result.Rules, Feedback: nil, }, @@ -1213,7 +1213,7 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti params := &rpc.ModelSwitchToRequest{ModelID: model} if opts != nil { params.ReasoningEffort = opts.ReasoningEffort - params.ModelCapabilities = convertModelCapabilitiesToClass(opts.ModelCapabilities) + params.ModelCapabilities = opts.ModelCapabilities } _, err := s.RPC.Model.SwitchTo(ctx, params) if err != nil { @@ -1223,34 +1223,6 @@ func (s *Session) SetModel(ctx context.Context, model string, opts *SetModelOpti return nil } -// convertModelCapabilitiesToClass converts from ModelCapabilitiesOverride -// (used in the public API) to ModelCapabilitiesClass (used internally by -// the ModelSwitchToRequest RPC). The two types are structurally identical -// but have different Go types due to code generation. -func convertModelCapabilitiesToClass(src *rpc.ModelCapabilitiesOverride) *rpc.ModelCapabilitiesClass { - if src == nil { - return nil - } - dst := &rpc.ModelCapabilitiesClass{ - Supports: src.Supports, - } - if src.Limits != nil { - dst.Limits = &rpc.ModelCapabilitiesLimitsClass{ - MaxContextWindowTokens: src.Limits.MaxContextWindowTokens, - MaxOutputTokens: src.Limits.MaxOutputTokens, - MaxPromptTokens: src.Limits.MaxPromptTokens, - } - if src.Limits.Vision != nil { - dst.Limits.Vision = &rpc.PurpleModelCapabilitiesOverrideLimitsVision{ - MaxPromptImageSize: src.Limits.Vision.MaxPromptImageSize, - MaxPromptImages: src.Limits.Vision.MaxPromptImages, - SupportedMediaTypes: src.Limits.Vision.SupportedMediaTypes, - } - } - } - return dst -} - type LogOptions struct { // Level sets the log severity. Valid values are [rpc.SessionLogLevelInfo] (default), // [rpc.SessionLogLevelWarning], and [rpc.SessionLogLevelError]. diff --git a/go/types.go b/go/types.go index aa4fafc94..4ef19c95b 100644 --- a/go/types.go +++ b/go/types.go @@ -862,7 +862,7 @@ type ( ModelCapabilitiesOverride = rpc.ModelCapabilitiesOverride ModelCapabilitiesOverrideSupports = rpc.ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideLimits = rpc.ModelCapabilitiesOverrideLimits - ModelCapabilitiesOverrideLimitsVision = rpc.PurpleModelCapabilitiesOverrideLimitsVision + ModelCapabilitiesOverrideLimitsVision = rpc.ModelCapabilitiesOverrideLimitsVision ) // ModelPolicy contains model policy state diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts index e1906a62d..a082eb659 100644 --- a/scripts/codegen/go.ts +++ b/scripts/codegen/go.ts @@ -148,7 +148,7 @@ function collapsePlaceholderGoStructs(code: string, knownDefinitionNames?: Set line.trim()) @@ -470,8 +470,15 @@ function resolveGoPropertyType( } if (nonNull.length > 1) { + // Resolve $refs in variants before discriminator analysis + const resolvedVariants = nonNull.map((v) => { + if (v.$ref && typeof v.$ref === "string") { + return resolveRef(v.$ref, ctx.definitions) ?? v; + } + return v; + }); // Check for discriminated union - const disc = findGoDiscriminator(nonNull); + const disc = findGoDiscriminator(resolvedVariants); if (disc) { const unionName = (propSchema.title as string) || nestedName; emitGoFlatDiscriminatedUnion(unionName, disc.property, disc.mapping, ctx, propSchema.description); @@ -946,7 +953,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): string { // Type aliases for types referenced by non-generated SDK code under their short names. const TYPE_ALIASES: Record = { PermissionRequestCommand: "PermissionRequestShellCommand", - PossibleURL: "PermissionRequestShellPossibleUrl", + PossibleURL: "PermissionRequestShellPossibleURL", Attachment: "UserMessageAttachment", AttachmentType: "UserMessageAttachmentType", }; @@ -1067,15 +1074,19 @@ async function generateRpc(schemaPath?: string): Promise { $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, }; - // Generate types via quicktype + // Generate types via quicktype — use a single combined schema source so quicktype + // sees each definition exactly once, preventing whimsical prefix disambiguation. const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(rootDefinitions)) { - const schemaWithDefs = withSharedDefinitions( - typeof def === "object" ? (def as JSONSchema7) : {}, - allDefinitionCollections - ); - await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); - } + const singleSchema: JSONSchema7 = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + definitions: allDefinitions as Record, + properties: Object.fromEntries( + Object.keys(rootDefinitions).map((name) => [name, { $ref: `#/definitions/${name}` }]) + ), + required: Object.keys(rootDefinitions), + }; + await schemaInput.addSource({ name: "RpcTypes", schema: JSON.stringify(singleSchema) }); const inputData = new InputData(); inputData.addInput(schemaInput); From 507f2dae408b6f823ba5fdde943addc499cd628f Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 17:32:33 +0100 Subject: [PATCH 06/10] So many more codegen fixes --- dotnet/src/Generated/Rpc.cs | 98 +- dotnet/src/Generated/SessionEvents.cs | 96 +- dotnet/src/Session.cs | 6 +- dotnet/test/SessionEventSerializationTests.cs | 25 +- nodejs/src/generated/rpc.ts | 1845 ++-- nodejs/src/generated/session-events.ts | 8182 +++++++++-------- nodejs/src/types.ts | 3 +- python/copilot/generated/rpc.py | 5082 +++++----- python/copilot/generated/session_events.py | 152 +- python/copilot/session.py | 14 +- scripts/codegen/csharp.ts | 52 +- scripts/codegen/python.ts | 20 +- 12 files changed, 8112 insertions(+), 7463 deletions(-) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index 4ebd19df9..f6435260c 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -1390,6 +1390,99 @@ public sealed class PermissionRequestResult public bool Success { get; set; } } +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionDecisionApproved), "approved")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByRules), "denied-by-rules")] +[JsonDerivedType(typeof(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedInteractivelyByUser), "denied-interactively-by-user")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] +[JsonDerivedType(typeof(PermissionDecisionDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +public partial class PermissionDecision +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The approved variant of . +public partial class PermissionDecisionApproved : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approved"; +} + +/// The denied-by-rules variant of . +public partial class PermissionDecisionDeniedByRules : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; + + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required object[] Rules { get; set; } +} + +/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +public partial class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; +} + +/// The denied-interactively-by-user variant of . +public partial class PermissionDecisionDeniedInteractivelyByUser : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-interactively-by-user"; + + /// Optional feedback from the user explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } +} + +/// The denied-by-content-exclusion-policy variant of . +public partial class PermissionDecisionDeniedByContentExclusionPolicy : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-content-exclusion-policy"; + + /// File path that triggered the exclusion. + [JsonPropertyName("path")] + public required string Path { get; set; } + + /// Human-readable explanation of why the path was excluded. + [JsonPropertyName("message")] + public required string Message { get; set; } +} + +/// The denied-by-permission-request-hook variant of . +public partial class PermissionDecisionDeniedByPermissionRequestHook : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; + + /// Optional message from the hook explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } + + /// Whether to interrupt the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interrupt")] + public bool? Interrupt { get; set; } +} + /// RPC data type for PermissionDecision operations. internal sealed class PermissionDecisionRequest { @@ -1403,7 +1496,7 @@ internal sealed class PermissionDecisionRequest /// Gets or sets the result value. [JsonPropertyName("result")] - public object Result { get; set; } = null!; + public PermissionDecision Result { get => field ??= new(); set; } } /// RPC data type for ShellExec operations. @@ -3020,7 +3113,7 @@ internal PermissionsApi(JsonRpc rpc, string sessionId) } /// Calls "session.permissions.handlePendingPermissionRequest". - public async Task HandlePendingPermissionRequestAsync(string requestId, object result, CancellationToken cancellationToken = default) + public async Task HandlePendingPermissionRequestAsync(string requestId, PermissionDecision result, CancellationToken cancellationToken = default) { var request = new PermissionDecisionRequest { SessionId = _sessionId, RequestId = requestId, Result = result }; return await CopilotClient.InvokeRpcAsync(_rpc, "session.permissions.handlePendingPermissionRequest", [request], cancellationToken); @@ -3306,6 +3399,7 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncPer-model usage breakdown, keyed by model identifier. [JsonPropertyName("modelMetrics")] - public required IDictionary ModelMetrics { get; set; } + public required IDictionary ModelMetrics { get; set; } /// Model that was selected at the time of shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -1920,7 +1920,7 @@ public partial class AssistantUsageData /// Per-quota resource usage snapshots, keyed by quota identifier. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("quotaSnapshots")] - public IDictionary? QuotaSnapshots { get; set; } + public IDictionary? QuotaSnapshots { get; set; } /// Per-request cost and usage data from the CAPI copilot_usage response field. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2771,6 +2771,57 @@ public partial class ShutdownCodeChanges public required string[] FilesModified { get; set; } } +/// Request count and cost metrics. +/// Nested data type for ShutdownModelMetricRequests. +public partial class ShutdownModelMetricRequests +{ + /// Total number of API requests made to this model. + [JsonPropertyName("count")] + public required double Count { get; set; } + + /// Cumulative cost multiplier for requests to this model. + [JsonPropertyName("cost")] + public required double Cost { get; set; } +} + +/// Token usage breakdown. +/// Nested data type for ShutdownModelMetricUsage. +public partial class ShutdownModelMetricUsage +{ + /// Total input tokens consumed across all requests to this model. + [JsonPropertyName("inputTokens")] + public required double InputTokens { get; set; } + + /// Total output tokens produced across all requests to this model. + [JsonPropertyName("outputTokens")] + public required double OutputTokens { get; set; } + + /// Total tokens read from prompt cache across all requests. + [JsonPropertyName("cacheReadTokens")] + public required double CacheReadTokens { get; set; } + + /// Total tokens written to prompt cache across all requests. + [JsonPropertyName("cacheWriteTokens")] + public required double CacheWriteTokens { get; set; } + + /// Total reasoning tokens produced across all requests to this model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reasoningTokens")] + public double? ReasoningTokens { get; set; } +} + +/// Nested data type for ShutdownModelMetric. +public partial class ShutdownModelMetric +{ + /// Request count and cost metrics. + [JsonPropertyName("requests")] + public required ShutdownModelMetricRequests Requests { get; set; } + + /// Token usage breakdown. + [JsonPropertyName("usage")] + public required ShutdownModelMetricUsage Usage { get; set; } +} + /// Token usage breakdown for the compaction LLM call. /// Nested data type for CompactionCompleteCompactionTokensUsed. public partial class CompactionCompleteCompactionTokensUsed @@ -3012,6 +3063,43 @@ public partial class AssistantMessageToolRequest public string? IntentionSummary { get; set; } } +/// Nested data type for AssistantUsageQuotaSnapshot. +public partial class AssistantUsageQuotaSnapshot +{ + /// Whether the user has an unlimited usage entitlement. + [JsonPropertyName("isUnlimitedEntitlement")] + public required bool IsUnlimitedEntitlement { get; set; } + + /// Total requests allowed by the entitlement. + [JsonPropertyName("entitlementRequests")] + public required double EntitlementRequests { get; set; } + + /// Number of requests already consumed. + [JsonPropertyName("usedRequests")] + public required double UsedRequests { get; set; } + + /// Whether usage is still permitted after quota exhaustion. + [JsonPropertyName("usageAllowedWithExhaustedQuota")] + public required bool UsageAllowedWithExhaustedQuota { get; set; } + + /// Number of requests over the entitlement limit. + [JsonPropertyName("overage")] + public required double Overage { get; set; } + + /// Whether overage is allowed when quota is exhausted. + [JsonPropertyName("overageAllowedWithExhaustedQuota")] + public required bool OverageAllowedWithExhaustedQuota { get; set; } + + /// Percentage of quota remaining (0.0 to 1.0). + [JsonPropertyName("remainingPercentage")] + public required double RemainingPercentage { get; set; } + + /// Date when the quota resets. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("resetDate")] + public DateTimeOffset? ResetDate { get; set; } +} + /// Token usage detail for a single billing category. /// Nested data type for AssistantUsageCopilotUsageTokenDetail. public partial class AssistantUsageCopilotUsageTokenDetail @@ -4182,6 +4270,7 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(AssistantUsageCopilotUsageTokenDetail))] [JsonSerializable(typeof(AssistantUsageData))] [JsonSerializable(typeof(AssistantUsageEvent))] +[JsonSerializable(typeof(AssistantUsageQuotaSnapshot))] [JsonSerializable(typeof(CapabilitiesChangedData))] [JsonSerializable(typeof(CapabilitiesChangedEvent))] [JsonSerializable(typeof(CapabilitiesChangedUI))] @@ -4302,6 +4391,9 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(SessionWorkspaceFileChangedData))] [JsonSerializable(typeof(SessionWorkspaceFileChangedEvent))] [JsonSerializable(typeof(ShutdownCodeChanges))] +[JsonSerializable(typeof(ShutdownModelMetric))] +[JsonSerializable(typeof(ShutdownModelMetricRequests))] +[JsonSerializable(typeof(ShutdownModelMetricUsage))] [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] [JsonSerializable(typeof(SkillsLoadedSkill))] diff --git a/dotnet/src/Session.cs b/dotnet/src/Session.cs index 20d6525b8..455cecdba 100644 --- a/dotnet/src/Session.cs +++ b/dotnet/src/Session.cs @@ -607,15 +607,15 @@ private async Task ExecutePermissionAndRespondAsync(string requestId, Permission { return; } - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, result); + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { Kind = result.Kind.Value }); } catch (Exception) { try { - await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionRequestResult + await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, new PermissionDecision { - Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser + Kind = PermissionRequestResultKind.DeniedCouldNotRequestFromUser.Value }); } catch (IOException) diff --git a/dotnet/test/SessionEventSerializationTests.cs b/dotnet/test/SessionEventSerializationTests.cs index 476867a4d..93e5ae935 100644 --- a/dotnet/test/SessionEventSerializationTests.cs +++ b/dotnet/test/SessionEventSerializationTests.cs @@ -93,22 +93,19 @@ public class SessionEventSerializationTests LinesRemoved = 0, FilesModified = ["README.md"], }, - ModelMetrics = new Dictionary + ModelMetrics = new Dictionary { - ["gpt-5.4"] = ParseJsonElement(""" + ["gpt-5.4"] = new ShutdownModelMetric + { + Requests = new ShutdownModelMetricRequests { Count = 1, Cost = 1 }, + Usage = new ShutdownModelMetricUsage { - "requests": { - "count": 1, - "cost": 1 - }, - "usage": { - "inputTokens": 10, - "outputTokens": 5, - "cacheReadTokens": 0, - "cacheWriteTokens": 0 - } - } - """), + InputTokens = 10, + OutputTokens = 5, + CacheReadTokens = 0, + CacheWriteTokens = 0, + }, + }, }, CurrentModel = "gpt-5.4", }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index ac1338d6f..cccad8f93 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -11,48 +11,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpServerConfig". */ -export type McpServerConfig = - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - type?: "local" | "stdio"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - command: string; - args: string[]; - cwd?: string; - env?: { - [k: string]: string; - }; - } - | { - /** - * Tools to include. Defaults to all tools if not specified. - */ - tools?: string[]; - /** - * Remote transport type. Defaults to "http" when omitted. - */ - type?: "http" | "sse"; - isDefaultServer?: boolean; - filterMapping?: FilterMapping; - /** - * Timeout in milliseconds for tool calls to this server. - */ - timeout?: number; - url: string; - headers?: { - [k: string]: string; - }; - oauthClientId?: string; - oauthPublicClient?: boolean; - }; +export type McpServerConfig = McpServerConfigLocal | McpServerConfigHttp; export type FilterMapping = | { @@ -77,66 +36,12 @@ export type UIElicitationResponseAction = "accept" | "decline" | "cancel"; export type UIElicitationFieldValue = string | number | boolean | string[]; export type PermissionDecision = - | { - /** - * The permission request was approved - */ - kind: "approved"; - } - | { - /** - * Denied because approval rules explicitly blocked it - */ - kind: "denied-by-rules"; - /** - * Rules that denied the request - */ - rules: unknown[]; - } - | { - /** - * Denied because no approval rule matched and user confirmation was unavailable - */ - kind: "denied-no-approval-rule-and-could-not-request-from-user"; - } - | { - /** - * Denied by the user during an interactive prompt - */ - kind: "denied-interactively-by-user"; - /** - * Optional feedback from the user explaining the denial - */ - feedback?: string; - } - | { - /** - * Denied by the organization's content exclusion policy - */ - kind: "denied-by-content-exclusion-policy"; - /** - * File path that triggered the exclusion - */ - path: string; - /** - * Human-readable explanation of why the path was excluded - */ - message: string; - } - | { - /** - * Denied by a permission request hook registered by an extension or plugin - */ - kind: "denied-by-permission-request-hook"; - /** - * Optional message from the hook explaining the denial - */ - message?: string; - /** - * Whether to interrupt the current agent turn - */ - interrupt?: boolean; - }; + | PermissionDecisionApproved + | PermissionDecisionDeniedByRules + | PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + | PermissionDecisionDeniedInteractivelyByUser + | PermissionDecisionDeniedByContentExclusionPolicy + | PermissionDecisionDeniedByPermissionRequestHook; /** * Log severity level. Determines how the message is displayed in the timeline. Defaults to "info". * @@ -145,6 +50,56 @@ export type PermissionDecision = */ export type SessionLogLevel = "info" | "warning" | "error"; +export interface PingRequest { + /** + * Optional message to echo back + */ + message?: string; +} + +export interface PingResult { + /** + * Echoed message (or default greeting) + */ + message: string; + /** + * Server timestamp in milliseconds + */ + timestamp: number; + /** + * Server protocol version number + */ + protocolVersion: number; +} + +export interface ModelList { + /** + * List of available models with full metadata + */ + models: Model[]; +} + +export interface Model { + /** + * Model identifier (e.g., "claude-sonnet-4.5") + */ + id: string; + /** + * Display name + */ + name: string; + capabilities: ModelCapabilities; + policy?: ModelPolicy; + billing?: ModelBilling; + /** + * Supported reasoning effort levels (only present if model supports reasoning effort) + */ + supportedReasoningEfforts?: string[]; + /** + * Default reasoning effort level (only present if model supports reasoning effort) + */ + defaultReasoningEffort?: string; +} /** * Model capabilities and limits * @@ -152,37 +107,45 @@ export type SessionLogLevel = "info" | "warning" | "error"; * via the `definition` "ModelCapabilities". */ export interface ModelCapabilities { + supports?: ModelCapabilitiesSupports; + limits?: ModelCapabilitiesLimits; +} +/** + * Feature flags indicating what the model supports + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesSupports". + */ +export interface ModelCapabilitiesSupports { /** - * Feature flags indicating what the model supports - */ - supports?: { - /** - * Whether this model supports vision/image input - */ - vision?: boolean; - /** - * Whether this model supports reasoning effort configuration - */ - reasoningEffort?: boolean; - }; + * Whether this model supports vision/image input + */ + vision?: boolean; /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - /** - * Maximum number of prompt/input tokens - */ - max_prompt_tokens?: number; - /** - * Maximum number of output/completion tokens - */ - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: ModelCapabilitiesLimitsVision; - }; + * Whether this model supports reasoning effort configuration + */ + reasoningEffort?: boolean; +} +/** + * Token limits for prompts, outputs, and context window + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesLimits". + */ +export interface ModelCapabilitiesLimits { + /** + * Maximum number of prompt/input tokens + */ + max_prompt_tokens?: number; + /** + * Maximum number of output/completion tokens + */ + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesLimitsVision; } /** * Vision-specific limits @@ -204,495 +167,272 @@ export interface ModelCapabilitiesLimitsVision { */ max_prompt_image_size: number; } - -export interface DiscoveredMcpServer { +/** + * Policy state (if applicable) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelPolicy". + */ +export interface ModelPolicy { /** - * Server name (config key) + * Current policy state for this model */ - name: string; + state: string; /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + * Usage terms or conditions for this model */ - type?: "stdio" | "http" | "sse" | "memory"; + terms: string; +} +/** + * Billing information + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelBilling". + */ +export interface ModelBilling { /** - * Configuration source + * Billing cost multiplier relative to the base rate */ - source: "user" | "workspace" | "plugin" | "builtin"; + multiplier: number; +} + +export interface ToolsListRequest { /** - * Whether the server is enabled (not in the disabled list) + * Optional model ID — when provided, the returned tool list reflects model-specific overrides */ - enabled: boolean; + model?: string; } -export interface ServerSkillList { +export interface ToolList { /** - * All discovered skills across all sources + * List of available built-in tools with metadata */ - skills: ServerSkill[]; + tools: Tool[]; } -export interface ServerSkill { +export interface Tool { /** - * Unique identifier for the skill + * Tool identifier (e.g., "bash", "grep", "str_replace_editor") */ name: string; /** - * Description of what the skill does - */ - description: string; - /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) + * Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) */ - source: string; + namespacedName?: string; /** - * Whether the skill can be invoked by the user as a slash command + * Description of what the tool does */ - userInvocable: boolean; + description: string; /** - * Whether the skill is currently enabled (based on global config) + * JSON Schema for the tool's input parameters */ - enabled: boolean; + parameters?: { + [k: string]: unknown; + }; /** - * Absolute path to the skill file + * Optional instructions for how to use this tool effectively */ - path?: string; + instructions?: string; +} + +export interface AccountGetQuotaResult { /** - * The project path this skill belongs to (only for project/inherited skills) + * Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) */ - projectPath?: string; + quotaSnapshots: { + [k: string]: AccountQuotaSnapshot; + }; } -export interface CurrentModel { +export interface AccountQuotaSnapshot { /** - * Currently active model identifier + * Number of requests included in the entitlement */ - modelId?: string; -} -/** - * Override individual model capabilities resolved by the runtime - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ModelCapabilitiesOverride". - */ -export interface ModelCapabilitiesOverride { + entitlementRequests: number; /** - * Feature flags indicating what the model supports + * Number of requests used so far this period */ - supports?: { - vision?: boolean; - reasoningEffort?: boolean; - }; + usedRequests: number; /** - * Token limits for prompts, outputs, and context window - */ - limits?: { - max_prompt_tokens?: number; - max_output_tokens?: number; - /** - * Maximum total context window size in tokens - */ - max_context_window_tokens?: number; - vision?: { - /** - * MIME types the model accepts - */ - supported_media_types?: string[]; - /** - * Maximum number of images per prompt - */ - max_prompt_images?: number; - /** - * Maximum image size in bytes - */ - max_prompt_image_size?: number; - }; - }; -} - -export interface AgentInfo { + * Percentage of entitlement remaining + */ + remainingPercentage: number; /** - * Unique identifier of the custom agent + * Number of overage requests made this period */ - name: string; + overage: number; /** - * Human-readable display name + * Whether pay-per-request usage is allowed when quota is exhausted */ - displayName: string; + overageAllowedWithExhaustedQuota: boolean; /** - * Description of the agent's purpose + * Date when the quota resets (ISO 8601) */ - description: string; + resetDate?: string; } -/** @experimental */ -export interface McpServerList { +export interface McpConfigList { /** - * Configured MCP servers + * All MCP servers from user config, keyed by name */ servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: "user" | "workspace" | "plugin" | "builtin"; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; + [k: string]: McpServerConfig; + }; } -export interface ToolCallResult { +export interface McpServerConfigLocal { /** - * Text result to send back to the LLM + * Tools to include. Defaults to all tools if not specified. */ - textResultForLlm: string; + tools?: string[]; + type?: "local" | "stdio"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; /** - * Type of the tool result + * Timeout in milliseconds for tool calls to this server. */ - resultType?: string; + timeout?: number; + command: string; + args: string[]; + cwd?: string; + env?: { + [k: string]: string; + }; +} + +export interface McpServerConfigHttp { /** - * Error message if the tool call failed + * Tools to include. Defaults to all tools if not specified. */ - error?: string; + tools?: string[]; /** - * Telemetry data from tool execution + * Remote transport type. Defaults to "http" when omitted. */ - toolTelemetry?: { - [k: string]: unknown; + type?: "http" | "sse"; + isDefaultServer?: boolean; + filterMapping?: FilterMapping; + /** + * Timeout in milliseconds for tool calls to this server. + */ + timeout?: number; + url: string; + headers?: { + [k: string]: string; }; + oauthClientId?: string; + oauthPublicClient?: boolean; } -export interface HandleToolCallResult { +export interface McpConfigAddRequest { /** - * Whether the tool call result was handled successfully + * Unique name for the MCP server */ - success: boolean; -} - -export interface UIElicitationStringEnumField { - type: "string"; - description?: string; - enum: string[]; - enumNames?: string[]; - default?: string; -} - -export interface UIElicitationStringOneOfField { - type: "string"; - description?: string; - oneOf: { - const: string; - }[]; - default?: string; -} - -export interface UIElicitationArrayEnumField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - type: "string"; - enum: string[]; - }; - default?: string[]; + name: string; + config: McpServerConfig; } -export interface UIElicitationArrayAnyOfField { - type: "array"; - description?: string; - minItems?: number; - maxItems?: number; - items: { - anyOf: { - const: string; - }[]; - }; - default?: string[]; -} -/** - * The elicitation response (accept with form values, decline, or cancel) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponse". - */ -export interface UIElicitationResponse { - action: UIElicitationResponseAction; - content?: UIElicitationResponseContent; -} -/** - * The form values submitted by the user (present when action is 'accept') - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "UIElicitationResponseContent". - */ -export interface UIElicitationResponseContent { - [k: string]: UIElicitationFieldValue; +export interface McpConfigUpdateRequest { + /** + * Name of the MCP server to update + */ + name: string; + config: McpServerConfig; } -export interface UIHandlePendingElicitationRequest { +export interface McpConfigRemoveRequest { /** - * The unique request ID from the elicitation.requested event + * Name of the MCP server to remove */ - requestId: string; - result: UIElicitationResponse; + name: string; } -export interface UIElicitationResult { +export interface McpDiscoverRequest { /** - * Whether the response was accepted. False if the request was already resolved by another client. + * Working directory used as context for discovery (e.g., plugin resolution) */ - success: boolean; + workingDirectory?: string; } -export interface PermissionDecisionRequest { +export interface McpDiscoverResult { /** - * Request ID of the pending permission request - */ - requestId: string; - result: PermissionDecision; -} - -export interface PermissionRequestResult { - /** - * Whether the permission request was handled successfully - */ - success: boolean; -} -/** - * Describes a filesystem error. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionFsError". - */ -export interface SessionFsError { - /** - * Error classification - */ - code: "ENOENT" | "UNKNOWN"; - /** - * Free-form detail about the error, for logging/diagnostics - */ - message?: string; -} - -export interface PingResult { - /** - * Echoed message (or default greeting) - */ - message: string; - /** - * Server timestamp in milliseconds - */ - timestamp: number; - /** - * Server protocol version number + * MCP servers discovered from all sources */ - protocolVersion: number; + servers: DiscoveredMcpServer[]; } -export interface PingRequest { +export interface DiscoveredMcpServer { /** - * Optional message to echo back + * Server name (config key) */ - message?: string; -} - -export interface ModelList { + name: string; /** - * List of available models with full metadata + * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) */ - models: { - /** - * Model identifier (e.g., "claude-sonnet-4.5") - */ - id: string; - /** - * Display name - */ - name: string; - capabilities: ModelCapabilities; - /** - * Policy state (if applicable) - */ - policy?: { - /** - * Current policy state for this model - */ - state: string; - /** - * Usage terms or conditions for this model - */ - terms: string; - }; - /** - * Billing information - */ - billing?: { - /** - * Billing cost multiplier relative to the base rate - */ - multiplier: number; - }; - /** - * Supported reasoning effort levels (only present if model supports reasoning effort) - */ - supportedReasoningEfforts?: string[]; - /** - * Default reasoning effort level (only present if model supports reasoning effort) - */ - defaultReasoningEffort?: string; - }[]; -} - -export interface ToolList { + type?: "stdio" | "http" | "sse" | "memory"; /** - * List of available built-in tools with metadata + * Configuration source */ - tools: { - /** - * Tool identifier (e.g., "bash", "grep", "str_replace_editor") - */ - name: string; - /** - * Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP tools) - */ - namespacedName?: string; - /** - * Description of what the tool does - */ - description: string; - /** - * JSON Schema for the tool's input parameters - */ - parameters?: { - [k: string]: unknown; - }; - /** - * Optional instructions for how to use this tool effectively - */ - instructions?: string; - }[]; -} - -export interface ToolsListRequest { + source: "user" | "workspace" | "plugin" | "builtin"; /** - * Optional model ID — when provided, the returned tool list reflects model-specific overrides + * Whether the server is enabled (not in the disabled list) */ - model?: string; + enabled: boolean; } -export interface AccountGetQuotaResult { +export interface SkillsConfigSetDisabledSkillsRequest { /** - * Quota snapshots keyed by type (e.g., chat, completions, premium_interactions) + * List of skill names to disable */ - quotaSnapshots: { - [k: string]: { - /** - * Number of requests included in the entitlement - */ - entitlementRequests: number; - /** - * Number of requests used so far this period - */ - usedRequests: number; - /** - * Percentage of entitlement remaining - */ - remainingPercentage: number; - /** - * Number of overage requests made this period - */ - overage: number; - /** - * Whether pay-per-request usage is allowed when quota is exhausted - */ - overageAllowedWithExhaustedQuota: boolean; - /** - * Date when the quota resets (ISO 8601) - */ - resetDate?: string; - }; - }; + disabledSkills: string[]; } -export interface McpConfigList { +export interface SkillsDiscoverRequest { /** - * All MCP servers from user config, keyed by name + * Optional list of project directory paths to scan for project-scoped skills */ - servers: { - [k: string]: McpServerConfig; - }; -} - -export interface McpConfigAddRequest { + projectPaths?: string[]; /** - * Unique name for the MCP server + * Optional list of additional skill directory paths to include */ - name: string; - config: McpServerConfig; + skillDirectories?: string[]; } -export interface McpConfigUpdateRequest { +export interface ServerSkillList { /** - * Name of the MCP server to update + * All discovered skills across all sources */ - name: string; - config: McpServerConfig; + skills: ServerSkill[]; } -export interface McpConfigRemoveRequest { +export interface ServerSkill { /** - * Name of the MCP server to remove + * Unique identifier for the skill */ name: string; -} - -export interface McpDiscoverResult { /** - * MCP servers discovered from all sources + * Description of what the skill does */ - servers: DiscoveredMcpServer[]; -} - -export interface McpDiscoverRequest { + description: string; /** - * Working directory used as context for discovery (e.g., plugin resolution) + * Source location type (e.g., project, personal-copilot, plugin, builtin) */ - workingDirectory?: string; -} - -export interface SkillsConfigSetDisabledSkillsRequest { + source: string; /** - * List of skill names to disable + * Whether the skill can be invoked by the user as a slash command */ - disabledSkills: string[]; -} - -export interface SkillsDiscoverRequest { + userInvocable: boolean; /** - * Optional list of project directory paths to scan for project-scoped skills + * Whether the skill is currently enabled (based on global config) */ - projectPaths?: string[]; + enabled: boolean; /** - * Optional list of additional skill directory paths to include + * Absolute path to the skill file */ - skillDirectories?: string[]; -} - -export interface SessionFsSetProviderResult { + path?: string; /** - * Whether the provider was set successfully + * The project path this skill belongs to (only for project/inherited skills) */ - success: boolean; + projectPath?: string; } export interface SessionFsSetProviderRequest { @@ -710,12 +450,11 @@ export interface SessionFsSetProviderRequest { conventions: "windows" | "posix"; } -/** @experimental */ -export interface SessionsForkResult { +export interface SessionFsSetProviderResult { /** - * The new forked session's ID + * Whether the provider was set successfully */ - sessionId: string; + success: boolean; } /** @experimental */ @@ -730,9 +469,17 @@ export interface SessionsForkRequest { toEventId?: string; } -export interface ModelSwitchToResult { +/** @experimental */ +export interface SessionsForkResult { /** - * Currently active model identifier after the switch + * The new forked session's ID + */ + sessionId: string; +} + +export interface CurrentModel { + /** + * Currently active model identifier */ modelId?: string; } @@ -748,6 +495,63 @@ export interface ModelSwitchToRequest { reasoningEffort?: string; modelCapabilities?: ModelCapabilitiesOverride; } +/** + * Override individual model capabilities resolved by the runtime + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverride". + */ +export interface ModelCapabilitiesOverride { + supports?: ModelCapabilitiesOverrideSupports; + limits?: ModelCapabilitiesOverrideLimits; +} +/** + * Feature flags indicating what the model supports + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverrideSupports". + */ +export interface ModelCapabilitiesOverrideSupports { + vision?: boolean; + reasoningEffort?: boolean; +} +/** + * Token limits for prompts, outputs, and context window + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ModelCapabilitiesOverrideLimits". + */ +export interface ModelCapabilitiesOverrideLimits { + max_prompt_tokens?: number; + max_output_tokens?: number; + /** + * Maximum total context window size in tokens + */ + max_context_window_tokens?: number; + vision?: ModelCapabilitiesOverrideLimitsVision; +} + +export interface ModelCapabilitiesOverrideLimitsVision { + /** + * MIME types the model accepts + */ + supported_media_types?: string[]; + /** + * Maximum number of images per prompt + */ + max_prompt_images?: number; + /** + * Maximum image size in bytes + */ + max_prompt_image_size?: number; +} + +export interface ModelSwitchToResult { + /** + * Currently active model identifier after the switch + */ + modelId?: string; +} export interface ModeSetRequest { mode: SessionMode; @@ -821,18 +625,18 @@ export interface WorkspacesListFilesResult { files: string[]; } -export interface WorkspacesReadFileResult { +export interface WorkspacesReadFileRequest { /** - * File content as a UTF-8 string + * Relative path within the workspace files directory */ - content: string; + path: string; } -export interface WorkspacesReadFileRequest { +export interface WorkspacesReadFileResult { /** - * Relative path within the workspace files directory + * File content as a UTF-8 string */ - path: string; + content: string; } export interface WorkspacesCreateFileRequest { @@ -850,48 +654,42 @@ export interface InstructionsGetSourcesResult { /** * Instruction sources for the session */ - sources: { - /** - * Unique identifier for this source (used for toggling) - */ - id: string; - /** - * Human-readable label - */ - label: string; - /** - * File path relative to repo or absolute for home - */ - sourcePath: string; - /** - * Raw content of the instruction file - */ - content: string; - /** - * Category of instruction source — used for merge logic - */ - type: "home" | "repo" | "model" | "vscode" | "nested-agents" | "child-instructions"; - /** - * Where this source lives — used for UI grouping - */ - location: "user" | "repository" | "working-directory"; - /** - * Glob pattern from frontmatter — when set, this instruction applies only to matching files - */ - applyTo?: string; - /** - * Short description (body after frontmatter) for use in instruction tables - */ - description?: string; - }[]; + sources: InstructionsSources[]; } -/** @experimental */ -export interface FleetStartResult { +export interface InstructionsSources { /** - * Whether fleet mode was successfully activated + * Unique identifier for this source (used for toggling) */ - started: boolean; + id: string; + /** + * Human-readable label + */ + label: string; + /** + * File path relative to repo or absolute for home + */ + sourcePath: string; + /** + * Raw content of the instruction file + */ + content: string; + /** + * Category of instruction source — used for merge logic + */ + type: "home" | "repo" | "model" | "vscode" | "nested-agents" | "child-instructions"; + /** + * Where this source lives — used for UI grouping + */ + location: "user" | "repository" | "working-directory"; + /** + * Glob pattern from frontmatter — when set, this instruction applies only to matching files + */ + applyTo?: string; + /** + * Short description (body after frontmatter) for use in instruction tables + */ + description?: string; } /** @experimental */ @@ -903,265 +701,517 @@ export interface FleetStartRequest { } /** @experimental */ -export interface AgentList { +export interface FleetStartResult { /** - * Available custom agents + * Whether fleet mode was successfully activated */ - agents: AgentInfo[]; + started: boolean; } /** @experimental */ -export interface AgentGetCurrentResult { +export interface AgentList { /** - * Currently selected custom agent, or null if using the default agent + * Available custom agents */ - agent?: AgentInfo | null; -} - -/** @experimental */ -export interface AgentSelectResult { - agent: AgentInfo; + agents: AgentInfo[]; } -/** @experimental */ -export interface AgentSelectRequest { +export interface AgentInfo { + /** + * Unique identifier of the custom agent + */ + name: string; + /** + * Human-readable display name + */ + displayName: string; + /** + * Description of the agent's purpose + */ + description: string; +} + +/** @experimental */ +export interface AgentGetCurrentResult { + /** + * Currently selected custom agent, or null if using the default agent + */ + agent?: AgentInfo | null; +} + +/** @experimental */ +export interface AgentSelectRequest { /** * Name of the custom agent to select */ name: string; } - -/** @experimental */ -export interface AgentReloadResult { - /** - * Reloaded custom agents - */ - agents: AgentInfo[]; + +/** @experimental */ +export interface AgentSelectResult { + agent: AgentInfo; +} + +/** @experimental */ +export interface AgentReloadResult { + /** + * Reloaded custom agents + */ + agents: AgentInfo[]; +} + +/** @experimental */ +export interface SkillList { + /** + * Available skills + */ + skills: Skill[]; +} + +export interface Skill { + /** + * Unique identifier for the skill + */ + name: string; + /** + * Description of what the skill does + */ + description: string; + /** + * Source location type (e.g., project, personal, plugin) + */ + source: string; + /** + * Whether the skill can be invoked by the user as a slash command + */ + userInvocable: boolean; + /** + * Whether the skill is currently enabled + */ + enabled: boolean; + /** + * Absolute path to the skill file + */ + path?: string; +} + +/** @experimental */ +export interface SkillsEnableRequest { + /** + * Name of the skill to enable + */ + name: string; +} + +/** @experimental */ +export interface SkillsDisableRequest { + /** + * Name of the skill to disable + */ + name: string; +} + +/** @experimental */ +export interface McpServerList { + /** + * Configured MCP servers + */ + servers: McpServer[]; +} + +export interface McpServer { + /** + * Server name (config key) + */ + name: string; + /** + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ + status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; + /** + * Configuration source: user, workspace, plugin, or builtin + */ + source?: "user" | "workspace" | "plugin" | "builtin"; + /** + * Error message if the server failed to connect + */ + error?: string; +} + +/** @experimental */ +export interface McpEnableRequest { + /** + * Name of the MCP server to enable + */ + serverName: string; +} + +/** @experimental */ +export interface McpDisableRequest { + /** + * Name of the MCP server to disable + */ + serverName: string; +} + +/** @experimental */ +export interface PluginList { + /** + * Installed plugins + */ + plugins: Plugin[]; +} + +export interface Plugin { + /** + * Plugin name + */ + name: string; + /** + * Marketplace the plugin came from + */ + marketplace: string; + /** + * Installed version + */ + version?: string; + /** + * Whether the plugin is currently enabled + */ + enabled: boolean; +} + +/** @experimental */ +export interface ExtensionList { + /** + * Discovered extensions and their current status + */ + extensions: Extension[]; +} + +export interface Extension { + /** + * Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') + */ + id: string; + /** + * Extension name (directory name) + */ + name: string; + /** + * Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) + */ + source: "project" | "user"; + /** + * Current status: running, disabled, failed, or starting + */ + status: "running" | "disabled" | "failed" | "starting"; + /** + * Process ID if the extension is running + */ + pid?: number; +} + +/** @experimental */ +export interface ExtensionsEnableRequest { + /** + * Source-qualified extension ID to enable + */ + id: string; +} + +/** @experimental */ +export interface ExtensionsDisableRequest { + /** + * Source-qualified extension ID to disable + */ + id: string; +} + +export interface ToolsHandlePendingToolCallRequest { + /** + * Request ID of the pending tool call + */ + requestId: string; + /** + * Tool call result (string or expanded result object) + */ + result?: string | ToolCallResult; + /** + * Error message if the tool call failed + */ + error?: string; +} + +export interface ToolCallResult { + /** + * Text result to send back to the LLM + */ + textResultForLlm: string; + /** + * Type of the tool result + */ + resultType?: string; + /** + * Error message if the tool call failed + */ + error?: string; + /** + * Telemetry data from tool execution + */ + toolTelemetry?: { + [k: string]: unknown; + }; +} + +export interface HandleToolCallResult { + /** + * Whether the tool call result was handled successfully + */ + success: boolean; +} + +export interface CommandsHandlePendingCommandRequest { + /** + * Request ID from the command invocation event + */ + requestId: string; + /** + * Error message if the command handler failed + */ + error?: string; +} + +export interface CommandsHandlePendingCommandResult { + /** + * Whether the command was handled successfully + */ + success: boolean; +} + +export interface UIElicitationRequest { + /** + * Message describing what information is needed from the user + */ + message: string; + requestedSchema: UIElicitationSchema; +} +/** + * JSON Schema describing the form fields to present to the user + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationSchema". + */ +export interface UIElicitationSchema { + /** + * Schema type indicator (always 'object') + */ + type: "object"; + /** + * Form field definitions, keyed by field name + */ + properties: { + [k: string]: + | UIElicitationStringEnumField + | UIElicitationStringOneOfField + | UIElicitationArrayEnumField + | UIElicitationArrayAnyOfField + | UIElicitationSchemaPropertyBoolean + | UIElicitationSchemaPropertyString + | UIElicitationSchemaPropertyNumber; + }; + /** + * List of required field names + */ + required?: string[]; +} + +export interface UIElicitationStringEnumField { + type: "string"; + description?: string; + enum: string[]; + enumNames?: string[]; + default?: string; +} + +export interface UIElicitationStringOneOfField { + type: "string"; + description?: string; + oneOf: UIElicitationStringOneOfFieldOneOf[]; + default?: string; +} + +export interface UIElicitationStringOneOfFieldOneOf { + const: string; +} + +export interface UIElicitationArrayEnumField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: UIElicitationArrayEnumFieldItems; + default?: string[]; +} + +export interface UIElicitationArrayEnumFieldItems { + type: "string"; + enum: string[]; +} + +export interface UIElicitationArrayAnyOfField { + type: "array"; + description?: string; + minItems?: number; + maxItems?: number; + items: UIElicitationArrayAnyOfFieldItems; + default?: string[]; +} + +export interface UIElicitationArrayAnyOfFieldItems { + anyOf: UIElicitationArrayAnyOfFieldItemsAnyOf[]; +} + +export interface UIElicitationArrayAnyOfFieldItemsAnyOf { + const: string; +} + +export interface UIElicitationSchemaPropertyBoolean { + type: "boolean"; + description?: string; + default?: boolean; +} + +export interface UIElicitationSchemaPropertyString { + type: "string"; + description?: string; + minLength?: number; + maxLength?: number; + format?: "email" | "uri" | "date" | "date-time"; + default?: string; +} + +export interface UIElicitationSchemaPropertyNumber { + type: "number" | "integer"; + description?: string; + minimum?: number; + maximum?: number; + default?: number; +} +/** + * The elicitation response (accept with form values, decline, or cancel) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponse". + */ +export interface UIElicitationResponse { + action: UIElicitationResponseAction; + content?: UIElicitationResponseContent; +} +/** + * The form values submitted by the user (present when action is 'accept') + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UIElicitationResponseContent". + */ +export interface UIElicitationResponseContent { + [k: string]: UIElicitationFieldValue; } -/** @experimental */ -export interface SkillList { +export interface UIHandlePendingElicitationRequest { /** - * Available skills + * The unique request ID from the elicitation.requested event */ - skills: { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type (e.g., project, personal, plugin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled - */ - enabled: boolean; - /** - * Absolute path to the skill file - */ - path?: string; - }[]; + requestId: string; + result: UIElicitationResponse; } -/** @experimental */ -export interface SkillsEnableRequest { +export interface UIElicitationResult { /** - * Name of the skill to enable + * Whether the response was accepted. False if the request was already resolved by another client. */ - name: string; + success: boolean; } -/** @experimental */ -export interface SkillsDisableRequest { +export interface PermissionDecisionRequest { /** - * Name of the skill to disable + * Request ID of the pending permission request */ - name: string; + requestId: string; + result: PermissionDecision; } -/** @experimental */ -export interface McpEnableRequest { +export interface PermissionDecisionApproved { /** - * Name of the MCP server to enable + * The permission request was approved */ - serverName: string; + kind: "approved"; } -/** @experimental */ -export interface McpDisableRequest { +export interface PermissionDecisionDeniedByRules { /** - * Name of the MCP server to disable + * Denied because approval rules explicitly blocked it */ - serverName: string; -} - -/** @experimental */ -export interface PluginList { + kind: "denied-by-rules"; /** - * Installed plugins + * Rules that denied the request */ - plugins: { - /** - * Plugin name - */ - name: string; - /** - * Marketplace the plugin came from - */ - marketplace: string; - /** - * Installed version - */ - version?: string; - /** - * Whether the plugin is currently enabled - */ - enabled: boolean; - }[]; + rules: unknown[]; } -/** @experimental */ -export interface ExtensionList { +export interface PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser { /** - * Discovered extensions and their current status + * Denied because no approval rule matched and user confirmation was unavailable */ - extensions: { - /** - * Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') - */ - id: string; - /** - * Extension name (directory name) - */ - name: string; - /** - * Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) - */ - source: "project" | "user"; - /** - * Current status: running, disabled, failed, or starting - */ - status: "running" | "disabled" | "failed" | "starting"; - /** - * Process ID if the extension is running - */ - pid?: number; - }[]; + kind: "denied-no-approval-rule-and-could-not-request-from-user"; } -/** @experimental */ -export interface ExtensionsEnableRequest { +export interface PermissionDecisionDeniedInteractivelyByUser { /** - * Source-qualified extension ID to enable + * Denied by the user during an interactive prompt */ - id: string; -} - -/** @experimental */ -export interface ExtensionsDisableRequest { + kind: "denied-interactively-by-user"; /** - * Source-qualified extension ID to disable + * Optional feedback from the user explaining the denial */ - id: string; + feedback?: string; } -export interface ToolsHandlePendingToolCallRequest { - /** - * Request ID of the pending tool call - */ - requestId: string; +export interface PermissionDecisionDeniedByContentExclusionPolicy { /** - * Tool call result (string or expanded result object) + * Denied by the organization's content exclusion policy */ - result?: string | ToolCallResult; + kind: "denied-by-content-exclusion-policy"; /** - * Error message if the tool call failed + * File path that triggered the exclusion */ - error?: string; -} - -export interface CommandsHandlePendingCommandResult { + path: string; /** - * Whether the command was handled successfully + * Human-readable explanation of why the path was excluded */ - success: boolean; + message: string; } -export interface CommandsHandlePendingCommandRequest { +export interface PermissionDecisionDeniedByPermissionRequestHook { /** - * Request ID from the command invocation event + * Denied by a permission request hook registered by an extension or plugin */ - requestId: string; + kind: "denied-by-permission-request-hook"; /** - * Error message if the command handler failed + * Optional message from the hook explaining the denial */ - error?: string; -} - -export interface UIElicitationRequest { + message?: string; /** - * Message describing what information is needed from the user + * Whether to interrupt the current agent turn */ - message: string; - /** - * JSON Schema describing the form fields to present to the user - */ - requestedSchema: { - /** - * Schema type indicator (always 'object') - */ - type: "object"; - /** - * Form field definitions, keyed by field name - */ - properties: { - [k: string]: - | UIElicitationStringEnumField - | UIElicitationStringOneOfField - | UIElicitationArrayEnumField - | UIElicitationArrayAnyOfField - | { - type: "boolean"; - description?: string; - default?: boolean; - } - | { - type: "string"; - description?: string; - minLength?: number; - maxLength?: number; - format?: "email" | "uri" | "date" | "date-time"; - default?: string; - } - | { - type: "number" | "integer"; - description?: string; - minimum?: number; - maximum?: number; - default?: number; - }; - }; - /** - * List of required field names - */ - required?: string[]; - }; + interrupt?: boolean; } -export interface LogResult { +export interface PermissionRequestResult { /** - * The unique identifier of the emitted session event + * Whether the permission request was handled successfully */ - eventId: string; + success: boolean; } export interface LogRequest { @@ -1180,11 +1230,11 @@ export interface LogRequest { url?: string; } -export interface ShellExecResult { +export interface LogResult { /** - * Unique identifier for tracking streamed output + * The unique identifier of the emitted session event */ - processId: string; + eventId: string; } export interface ShellExecRequest { @@ -1202,11 +1252,11 @@ export interface ShellExecRequest { timeout?: number; } -export interface ShellKillResult { +export interface ShellExecResult { /** - * Whether the signal was sent successfully + * Unique identifier for tracking streamed output */ - killed: boolean; + processId: string; } export interface ShellKillRequest { @@ -1220,6 +1270,13 @@ export interface ShellKillRequest { signal?: "SIGTERM" | "SIGKILL" | "SIGINT"; } +export interface ShellKillResult { + /** + * Whether the signal was sent successfully + */ + killed: boolean; +} + /** @experimental */ export interface HistoryCompactResult { /** @@ -1234,43 +1291,39 @@ export interface HistoryCompactResult { * Number of messages removed during compaction */ messagesRemoved: number; - /** - * Post-compaction context window usage breakdown - */ - contextWindow?: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Current total tokens in the context window (system + conversation + tool definitions) - */ - currentTokens: number; - /** - * Current number of messages in the conversation - */ - messagesLength: number; - /** - * Token count from system message(s) - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) - */ - conversationTokens?: number; - /** - * Token count from tool definitions - */ - toolDefinitionsTokens?: number; - }; + contextWindow?: HistoryCompactContextWindow; } - -/** @experimental */ -export interface HistoryTruncateResult { +/** + * Post-compaction context window usage breakdown + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "HistoryCompactContextWindow". + */ +export interface HistoryCompactContextWindow { /** - * Number of events that were removed + * Maximum token count for the model's context window */ - eventsRemoved: number; + tokenLimit: number; + /** + * Current total tokens in the context window (system + conversation + tool definitions) + */ + currentTokens: number; + /** + * Current number of messages in the conversation + */ + messagesLength: number; + /** + * Token count from system message(s) + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) + */ + conversationTokens?: number; + /** + * Token count from tool definitions + */ + toolDefinitionsTokens?: number; } /** @experimental */ @@ -1281,6 +1334,14 @@ export interface HistoryTruncateRequest { eventId: string; } +/** @experimental */ +export interface HistoryTruncateResult { + /** + * Number of events that were removed + */ + eventsRemoved: number; +} + /** @experimental */ export interface UsageGetMetricsResult { /** @@ -1299,67 +1360,12 @@ export interface UsageGetMetricsResult { * Session start timestamp (epoch milliseconds) */ sessionStartTime: number; - /** - * Aggregated code change metrics - */ - codeChanges: { - /** - * Total lines of code added - */ - linesAdded: number; - /** - * Total lines of code removed - */ - linesRemoved: number; - /** - * Number of distinct files modified - */ - filesModifiedCount: number; - }; + codeChanges: UsageMetricsCodeChanges; /** * Per-model token and request metrics, keyed by model identifier */ modelMetrics: { - [k: string]: { - /** - * Request count and cost metrics for this model - */ - requests: { - /** - * Number of API requests made with this model - */ - count: number; - /** - * User-initiated premium request cost (with multiplier applied) - */ - cost: number; - }; - /** - * Token usage metrics for this model - */ - usage: { - /** - * Total input tokens consumed - */ - inputTokens: number; - /** - * Total output tokens produced - */ - outputTokens: number; - /** - * Total tokens read from prompt cache - */ - cacheReadTokens: number; - /** - * Total tokens written to prompt cache - */ - cacheWriteTokens: number; - /** - * Total output tokens used for reasoning - */ - reasoningTokens?: number; - }; - }; + [k: string]: UsageMetricsModelMetric; }; /** * Currently active model identifier @@ -1374,13 +1380,74 @@ export interface UsageGetMetricsResult { */ lastCallOutputTokens: number; } +/** + * Aggregated code change metrics + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsCodeChanges". + */ +export interface UsageMetricsCodeChanges { + /** + * Total lines of code added + */ + linesAdded: number; + /** + * Total lines of code removed + */ + linesRemoved: number; + /** + * Number of distinct files modified + */ + filesModifiedCount: number; +} -export interface SessionFsReadFileResult { +export interface UsageMetricsModelMetric { + requests: UsageMetricsModelMetricRequests; + usage: UsageMetricsModelMetricUsage; +} +/** + * Request count and cost metrics for this model + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsModelMetricRequests". + */ +export interface UsageMetricsModelMetricRequests { /** - * File content as UTF-8 string + * Number of API requests made with this model */ - content: string; - error?: SessionFsError; + count: number; + /** + * User-initiated premium request cost (with multiplier applied) + */ + cost: number; +} +/** + * Token usage metrics for this model + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "UsageMetricsModelMetricUsage". + */ +export interface UsageMetricsModelMetricUsage { + /** + * Total input tokens consumed + */ + inputTokens: number; + /** + * Total output tokens produced + */ + outputTokens: number; + /** + * Total tokens read from prompt cache + */ + cacheReadTokens: number; + /** + * Total tokens written to prompt cache + */ + cacheWriteTokens: number; + /** + * Total output tokens used for reasoning + */ + reasoningTokens?: number; } export interface SessionFsReadFileRequest { @@ -1394,6 +1461,30 @@ export interface SessionFsReadFileRequest { path: string; } +export interface SessionFsReadFileResult { + /** + * File content as UTF-8 string + */ + content: string; + error?: SessionFsError; +} +/** + * Describes a filesystem error. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsError". + */ +export interface SessionFsError { + /** + * Error classification + */ + code: "ENOENT" | "UNKNOWN"; + /** + * Free-form detail about the error, for logging/diagnostics + */ + message?: string; +} + export interface SessionFsWriteFileRequest { /** * Target session identifier @@ -1432,6 +1523,17 @@ export interface SessionFsAppendFileRequest { mode?: number; } +export interface SessionFsExistsRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * Path using SessionFs conventions + */ + path: string; +} + export interface SessionFsExistsResult { /** * Whether the path exists @@ -1439,7 +1541,7 @@ export interface SessionFsExistsResult { exists: boolean; } -export interface SessionFsExistsRequest { +export interface SessionFsStatRequest { /** * Target session identifier */ @@ -1474,7 +1576,7 @@ export interface SessionFsStatResult { error?: SessionFsError; } -export interface SessionFsStatRequest { +export interface SessionFsMkdirRequest { /** * Target session identifier */ @@ -1483,9 +1585,17 @@ export interface SessionFsStatRequest { * Path using SessionFs conventions */ path: string; + /** + * Create parent directories as needed + */ + recursive?: boolean; + /** + * Optional POSIX-style mode for newly created directories + */ + mode?: number; } -export interface SessionFsMkdirRequest { +export interface SessionFsReaddirRequest { /** * Target session identifier */ @@ -1494,14 +1604,6 @@ export interface SessionFsMkdirRequest { * Path using SessionFs conventions */ path: string; - /** - * Create parent directories as needed - */ - recursive?: boolean; - /** - * Optional POSIX-style mode for newly created directories - */ - mode?: number; } export interface SessionFsReaddirResult { @@ -1512,7 +1614,7 @@ export interface SessionFsReaddirResult { error?: SessionFsError; } -export interface SessionFsReaddirRequest { +export interface SessionFsReaddirWithTypesRequest { /** * Target session identifier */ @@ -1527,28 +1629,19 @@ export interface SessionFsReaddirWithTypesResult { /** * Directory entries with type information */ - entries: { - /** - * Entry name - */ - name: string; - /** - * Entry type - */ - type: "file" | "directory"; - }[]; + entries: SessionFsReaddirWithTypesEntry[]; error?: SessionFsError; } -export interface SessionFsReaddirWithTypesRequest { +export interface SessionFsReaddirWithTypesEntry { /** - * Target session identifier + * Entry name */ - sessionId: string; + name: string; /** - * Path using SessionFs conventions + * Entry type */ - path: string; + type: "file" | "directory"; } export interface SessionFsRmRequest { diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 4a5b12ad2..3f248641a 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -4,3951 +4,147 @@ */ export type SessionEvent = - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.start"; - /** - * Session initialization metadata including context and configuration - */ - data: { - /** - * Unique identifier for the session - */ - sessionId: string; - /** - * Schema version number for the session event format - */ - version: number; - /** - * Identifier of the software producing the events (e.g., "copilot-agent") - */ - producer: string; - /** - * Version string of the Copilot application - */ - copilotVersion: string; - /** - * ISO 8601 timestamp when the session was created - */ - startTime: string; - /** - * Model selected at session creation time, if any - */ - selectedModel?: string; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - context?: WorkingDirectoryContext; - /** - * Whether the session was already in use by another client at start time - */ - alreadyInUse?: boolean; - /** - * Whether this session supports remote steering via Mission Control - */ - remoteSteerable?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.resume"; - /** - * Session resume metadata including current context and event count - */ - data: { - /** - * ISO 8601 timestamp when the session was resumed - */ - resumeTime: string; - /** - * Total number of persisted events in the session at the time of resume - */ - eventCount: number; - /** - * Model currently selected at resume time - */ - selectedModel?: string; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - context?: WorkingDirectoryContext; - /** - * Whether the session was already in use by another client at resume time - */ - alreadyInUse?: boolean; - /** - * Whether this session supports remote steering via Mission Control - */ - remoteSteerable?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.remote_steerable_changed"; - /** - * Notifies Mission Control that the session's remote steering capability has changed - */ - data: { - /** - * Whether this session now supports remote steering via Mission Control - */ - remoteSteerable: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.error"; - /** - * Error details for timeline display including message and optional diagnostic information - */ - data: { - /** - * Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") - */ - errorType: string; - /** - * Human-readable error message - */ - message: string; - /** - * Error stack trace, when available - */ - stack?: string; - /** - * HTTP status code from the upstream request, if applicable - */ - statusCode?: number; - /** - * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - */ - providerCallId?: string; - /** - * Optional URL associated with this error that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.idle"; - /** - * Payload indicating the session is idle with no background agents in flight - */ - data: { - /** - * True when the preceding agentic loop was cancelled via abort signal - */ - aborted?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.title_changed"; - /** - * Session title change payload containing the new display title - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.info"; - /** - * Informational message for timeline display with categorization - */ - data: { - /** - * Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") - */ - infoType: string; - /** - * Human-readable informational message for display in the timeline - */ - message: string; - /** - * Optional URL associated with this message that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.warning"; - /** - * Warning message for timeline display with categorization - */ - data: { - /** - * Category of warning (e.g., "subscription", "policy", "mcp") - */ - warningType: string; - /** - * Human-readable warning message for display in the timeline - */ - message: string; - /** - * Optional URL associated with this warning that the user can open in a browser - */ - url?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.model_change"; - /** - * Model change details including previous and new model identifiers - */ - data: { - /** - * Model that was previously selected, if any - */ - previousModel?: string; - /** - * Newly selected model identifier - */ - newModel: string; - /** - * Reasoning effort level before the model change, if applicable - */ - previousReasoningEffort?: string; - /** - * Reasoning effort level after the model change, if applicable - */ - reasoningEffort?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mode_changed"; - /** - * Agent mode change details including previous and new modes - */ - data: { - /** - * Agent mode before the change (e.g., "interactive", "plan", "autopilot") - */ - previousMode: string; - /** - * Agent mode after the change (e.g., "interactive", "plan", "autopilot") - */ - newMode: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.plan_changed"; - /** - * Plan file operation details indicating what changed - */ - data: { - /** - * The type of operation performed on the plan file - */ - operation: "create" | "update" | "delete"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.workspace_file_changed"; - /** - * Workspace file change details including path and operation type - */ - data: { - /** - * Relative path within the session workspace files directory - */ - path: string; - /** - * Whether the file was newly created or updated - */ - operation: "create" | "update"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.handoff"; - /** - * Session handoff metadata including source, context, and repository information - */ - data: { - /** - * ISO 8601 timestamp when the handoff occurred - */ - handoffTime: string; - /** - * Origin type of the session being handed off - */ - sourceType: "remote" | "local"; - /** - * Repository context for the handed-off session - */ - repository?: { - /** - * Repository owner (user or organization) - */ - owner: string; - /** - * Repository name - */ - name: string; - /** - * Git branch name, if applicable - */ - branch?: string; - }; - /** - * Additional context information for the handoff - */ - context?: string; - /** - * Summary of the work done in the source session - */ - summary?: string; - /** - * Session ID of the remote session being handed off - */ - remoteSessionId?: string; - /** - * GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) - */ - host?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.truncation"; - /** - * Conversation truncation statistics including token counts and removed content metrics - */ - data: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Total tokens in conversation messages before truncation - */ - preTruncationTokensInMessages: number; - /** - * Number of conversation messages before truncation - */ - preTruncationMessagesLength: number; - /** - * Total tokens in conversation messages after truncation - */ - postTruncationTokensInMessages: number; - /** - * Number of conversation messages after truncation - */ - postTruncationMessagesLength: number; - /** - * Number of tokens removed by truncation - */ - tokensRemovedDuringTruncation: number; - /** - * Number of messages removed by truncation - */ - messagesRemovedDuringTruncation: number; - /** - * Identifier of the component that performed truncation (e.g., "BasicTruncator") - */ - performedBy: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.snapshot_rewind"; - /** - * Session rewind details including target event and count of removed events - */ - data: { - /** - * Event ID that was rewound to; this event and all after it were removed - */ - upToEventId: string; - /** - * Number of events that were removed by the rewind - */ - eventsRemoved: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.shutdown"; - /** - * Session termination metrics including usage statistics, code changes, and shutdown reason - */ - data: { - /** - * Whether the session ended normally ("routine") or due to a crash/fatal error ("error") - */ - shutdownType: "routine" | "error"; - /** - * Error description when shutdownType is "error" - */ - errorReason?: string; - /** - * Total number of premium API requests used during the session - */ - totalPremiumRequests: number; - /** - * Cumulative time spent in API calls during the session, in milliseconds - */ - totalApiDurationMs: number; - /** - * Unix timestamp (milliseconds) when the session started - */ - sessionStartTime: number; - /** - * Aggregate code change metrics for the session - */ - codeChanges: { - /** - * Total number of lines added during the session - */ - linesAdded: number; - /** - * Total number of lines removed during the session - */ - linesRemoved: number; - /** - * List of file paths that were modified during the session - */ - filesModified: string[]; - }; - /** - * Per-model usage breakdown, keyed by model identifier - */ - modelMetrics: { - [k: string]: { - /** - * Request count and cost metrics - */ - requests: { - /** - * Total number of API requests made to this model - */ - count: number; - /** - * Cumulative cost multiplier for requests to this model - */ - cost: number; - }; - /** - * Token usage breakdown - */ - usage: { - /** - * Total input tokens consumed across all requests to this model - */ - inputTokens: number; - /** - * Total output tokens produced across all requests to this model - */ - outputTokens: number; - /** - * Total tokens read from prompt cache across all requests - */ - cacheReadTokens: number; - /** - * Total tokens written to prompt cache across all requests - */ - cacheWriteTokens: number; - /** - * Total reasoning tokens produced across all requests to this model - */ - reasoningTokens?: number; - }; - }; - }; - /** - * Model that was selected at the time of shutdown - */ - currentModel?: string; - /** - * Total tokens in context window at shutdown - */ - currentTokens?: number; - /** - * System message token count at shutdown - */ - systemTokens?: number; - /** - * Non-system message token count at shutdown - */ - conversationTokens?: number; - /** - * Tool definitions token count at shutdown - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.context_changed"; - data: WorkingDirectoryContext; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.usage_info"; - /** - * Current context window usage statistics including token and message counts - */ - data: { - /** - * Maximum token count for the model's context window - */ - tokenLimit: number; - /** - * Current number of tokens in the context window - */ - currentTokens: number; - /** - * Current number of messages in the conversation - */ - messagesLength: number; - /** - * Token count from system message(s) - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) - */ - conversationTokens?: number; - /** - * Token count from tool definitions - */ - toolDefinitionsTokens?: number; - /** - * Whether this is the first usage_info event emitted in this session - */ - isInitial?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.compaction_start"; - /** - * Context window breakdown at the start of LLM-powered conversation compaction - */ - data: { - /** - * Token count from system message(s) at compaction start - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) at compaction start - */ - conversationTokens?: number; - /** - * Token count from tool definitions at compaction start - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.compaction_complete"; - /** - * Conversation compaction results including success status, metrics, and optional error details - */ - data: { - /** - * Whether compaction completed successfully - */ - success: boolean; - /** - * Error message if compaction failed - */ - error?: string; - /** - * Total tokens in conversation before compaction - */ - preCompactionTokens?: number; - /** - * Total tokens in conversation after compaction - */ - postCompactionTokens?: number; - /** - * Number of messages before compaction - */ - preCompactionMessagesLength?: number; - /** - * Number of messages removed during compaction - */ - messagesRemoved?: number; - /** - * Number of tokens removed during compaction - */ - tokensRemoved?: number; - /** - * LLM-generated summary of the compacted conversation history - */ - summaryContent?: string; - /** - * Checkpoint snapshot number created for recovery - */ - checkpointNumber?: number; - /** - * File path where the checkpoint was stored - */ - checkpointPath?: string; - /** - * Token usage breakdown for the compaction LLM call - */ - compactionTokensUsed?: { - /** - * Input tokens consumed by the compaction LLM call - */ - input: number; - /** - * Output tokens produced by the compaction LLM call - */ - output: number; - /** - * Cached input tokens reused in the compaction LLM call - */ - cachedInput: number; - }; - /** - * GitHub request tracing ID (x-github-request-id header) for the compaction LLM call - */ - requestId?: string; - /** - * Token count from system message(s) after compaction - */ - systemTokens?: number; - /** - * Token count from non-system messages (user, assistant, tool) after compaction - */ - conversationTokens?: number; - /** - * Token count from tool definitions after compaction - */ - toolDefinitionsTokens?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.task_complete"; - /** - * Task completion notification with summary from the agent - */ - data: { - /** - * Summary of the completed task, provided by the agent - */ - summary?: string; - /** - * Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) - */ - success?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user.message"; - data: { - /** - * The user's message text as displayed in the timeline - */ - content: string; - /** - * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching - */ - transformedContent?: string; - /** - * Files, selections, or GitHub references attached to the message - */ - attachments?: ( - | { - /** - * Attachment type discriminator - */ - type: "file"; - /** - * Absolute file path - */ - path: string; - /** - * User-facing display name for the attachment - */ - displayName: string; - /** - * Optional line range to scope the attachment to a specific section of the file - */ - lineRange?: { - /** - * Start line number (1-based) - */ - start: number; - /** - * End line number (1-based, inclusive) - */ - end: number; - }; - } - | { - /** - * Attachment type discriminator - */ - type: "directory"; - /** - * Absolute directory path - */ - path: string; - /** - * User-facing display name for the attachment - */ - displayName: string; - } - | { - /** - * Attachment type discriminator - */ - type: "selection"; - /** - * Absolute path to the file containing the selection - */ - filePath: string; - /** - * User-facing display name for the selection - */ - displayName: string; - /** - * The selected text content - */ - text: string; - /** - * Position range of the selection within the file - */ - selection: { - /** - * Start position of the selection - */ - start: { - /** - * Start line number (0-based) - */ - line: number; - /** - * Start character offset within the line (0-based) - */ - character: number; - }; - /** - * End position of the selection - */ - end: { - /** - * End line number (0-based) - */ - line: number; - /** - * End character offset within the line (0-based) - */ - character: number; - }; - }; - } - | { - /** - * Attachment type discriminator - */ - type: "github_reference"; - /** - * Issue, pull request, or discussion number - */ - number: number; - /** - * Type of GitHub reference - */ - referenceType: "issue" | "pr" | "discussion"; - /** - * Current state of the referenced item (e.g., open, closed, merged) - */ - state: string; - /** - * URL to the referenced item on GitHub - */ - url: string; - } - | { - /** - * Attachment type discriminator - */ - type: "blob"; - /** - * Base64-encoded content - */ - data: string; - /** - * MIME type of the inline data - */ - mimeType: string; - /** - * User-facing display name for the attachment - */ - displayName?: string; - } - )[]; - /** - * Normalized document MIME types that were sent natively instead of through tagged_files XML - */ - supportedNativeDocumentMimeTypes?: string[]; - /** - * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit - */ - nativeDocumentPathFallbackPaths?: string[]; - /** - * 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; - /** - * The agent mode that was active when this message was sent - */ - agentMode?: "interactive" | "plan" | "autopilot" | "shell"; - /** - * CAPI interaction ID for correlating this user message with its turn - */ - interactionId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "pending_messages.modified"; - /** - * Empty payload; the event signals that the pending message queue has changed - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.turn_start"; - /** - * Turn initialization metadata including identifier and interaction tracking - */ - data: { - /** - * Identifier for this turn within the agentic loop, typically a stringified turn number - */ - turnId: string; - /** - * CAPI interaction ID for correlating this turn with upstream telemetry - */ - interactionId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.intent"; - /** - * Agent intent description for current activity or plan - */ - data: { - /** - * Short description of what the agent is currently doing or planning to do - */ - intent: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.reasoning"; - /** - * Assistant reasoning content for timeline display with complete thinking text - */ - data: { - /** - * Unique identifier for this reasoning block - */ - reasoningId: string; - /** - * The complete extended thinking text from the model - */ - content: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.reasoning_delta"; - /** - * Streaming reasoning delta for incremental extended thinking updates - */ - data: { - /** - * Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event - */ - reasoningId: string; - /** - * Incremental text chunk to append to the reasoning content - */ - deltaContent: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.streaming_delta"; - /** - * Streaming response progress with cumulative byte count - */ - data: { - /** - * Cumulative total bytes received from the streaming response so far - */ - totalResponseSizeBytes: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.message"; - /** - * Assistant response containing text content, optional tool requests, and interaction metadata - */ - data: { - /** - * Unique identifier for this assistant message - */ - messageId: string; - /** - * The assistant's text response content - */ - content: string; - /** - * Tool invocations requested by the assistant in this message - */ - toolRequests?: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool being invoked - */ - name: string; - /** - * Arguments to pass to the tool, format depends on the tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. - */ - type?: "function" | "custom"; - /** - * Human-readable display title for the tool - */ - toolTitle?: string; - /** - * Name of the MCP server hosting this tool, when the tool is an MCP tool - */ - mcpServerName?: string; - /** - * Resolved intention summary describing what this specific call does - */ - intentionSummary?: string | null; - }[]; - /** - * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. - */ - reasoningOpaque?: string; - /** - * Readable reasoning text from the model's extended thinking - */ - reasoningText?: string; - /** - * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. - */ - encryptedContent?: string; - /** - * Generation phase for phased-output models (e.g., thinking vs. response phases) - */ - phase?: string; - /** - * Actual output token count from the API response (completion_tokens), used for accurate token accounting - */ - outputTokens?: number; - /** - * CAPI interaction ID for correlating this message with upstream telemetry - */ - interactionId?: string; - /** - * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs - */ - requestId?: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.message_delta"; - /** - * Streaming assistant message delta for incremental response updates - */ - data: { - /** - * Message ID this delta belongs to, matching the corresponding assistant.message event - */ - messageId: string; - /** - * Incremental text chunk to append to the message content - */ - deltaContent: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.turn_end"; - /** - * Turn completion metadata including the turn identifier - */ - data: { - /** - * Identifier of the turn that has ended, matching the corresponding assistant.turn_start event - */ - turnId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "assistant.usage"; - /** - * LLM API call usage metrics including tokens, costs, quotas, and billing information - */ - data: { - /** - * Model identifier used for this API call - */ - model: string; - /** - * Number of input tokens consumed - */ - inputTokens?: number; - /** - * Number of output tokens produced - */ - outputTokens?: number; - /** - * Number of tokens read from prompt cache - */ - cacheReadTokens?: number; - /** - * Number of tokens written to prompt cache - */ - cacheWriteTokens?: number; - /** - * Number of output tokens used for reasoning (e.g., chain-of-thought) - */ - reasoningTokens?: number; - /** - * Model multiplier cost for billing purposes - */ - cost?: number; - /** - * Duration of the API call in milliseconds - */ - duration?: number; - /** - * Time to first token in milliseconds. Only available for streaming requests - */ - ttftMs?: number; - /** - * Average inter-token latency in milliseconds. Only available for streaming requests - */ - interTokenLatencyMs?: number; - /** - * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls - */ - initiator?: string; - /** - * Completion ID from the model provider (e.g., chatcmpl-abc123) - */ - apiCallId?: string; - /** - * GitHub request tracing ID (x-github-request-id header) for server-side log correlation - */ - providerCallId?: string; - /** - * @deprecated - * Parent tool call ID when this usage originates from a sub-agent - */ - parentToolCallId?: string; - /** - * Per-quota resource usage snapshots, keyed by quota identifier - */ - quotaSnapshots?: { - [k: string]: { - /** - * Whether the user has an unlimited usage entitlement - */ - isUnlimitedEntitlement: boolean; - /** - * Total requests allowed by the entitlement - */ - entitlementRequests: number; - /** - * Number of requests already consumed - */ - usedRequests: number; - /** - * Whether usage is still permitted after quota exhaustion - */ - usageAllowedWithExhaustedQuota: boolean; - /** - * Number of requests over the entitlement limit - */ - overage: number; - /** - * Whether overage is allowed when quota is exhausted - */ - overageAllowedWithExhaustedQuota: boolean; - /** - * Percentage of quota remaining (0.0 to 1.0) - */ - remainingPercentage: number; - /** - * Date when the quota resets - */ - resetDate?: string; - }; - }; - /** - * Per-request cost and usage data from the CAPI copilot_usage response field - */ - copilotUsage?: { - /** - * Itemized token usage breakdown - */ - tokenDetails: { - /** - * Number of tokens in this billing batch - */ - batchSize: number; - /** - * Cost per batch of tokens - */ - costPerBatch: number; - /** - * Total token count for this entry - */ - tokenCount: number; - /** - * Token category (e.g., "input", "output") - */ - tokenType: string; - }[]; - /** - * Total cost in nano-AIU (AI Units) for this request - */ - totalNanoAiu: number; - }; - /** - * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") - */ - reasoningEffort?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "abort"; - /** - * Turn abort information including the reason for termination - */ - data: { - /** - * Reason the current turn was aborted (e.g., "user initiated") - */ - reason: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.user_requested"; - /** - * User-initiated tool invocation request with tool name and arguments - */ - data: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool the user wants to invoke - */ - toolName: string; - /** - * Arguments for the tool invocation - */ - arguments?: { - [k: string]: unknown; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_start"; - /** - * Tool execution startup details including MCP server information when applicable - */ - data: { - /** - * Unique identifier for this tool call - */ - toolCallId: string; - /** - * Name of the tool being executed - */ - toolName: string; - /** - * Arguments passed to the tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * Name of the MCP server hosting this tool, when the tool is an MCP tool - */ - mcpServerName?: string; - /** - * Original tool name on the MCP server, when the tool is an MCP tool - */ - mcpToolName?: string; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_partial_result"; - /** - * Streaming tool execution output for incremental result display - */ - data: { - /** - * Tool call ID this partial result belongs to - */ - toolCallId: string; - /** - * Incremental output chunk from the running tool - */ - partialOutput: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_progress"; - /** - * Tool execution progress notification with status message - */ - data: { - /** - * Tool call ID this progress notification belongs to - */ - toolCallId: string; - /** - * Human-readable progress status message (e.g., from an MCP server) - */ - progressMessage: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "tool.execution_complete"; - /** - * Tool execution completion results including success status, detailed output, and error information - */ - data: { - /** - * Unique identifier for the completed tool call - */ - toolCallId: string; - /** - * Whether the tool execution completed successfully - */ - success: boolean; - /** - * Model identifier that generated this tool call - */ - model?: string; - /** - * CAPI interaction ID for correlating this tool execution with upstream telemetry - */ - interactionId?: string; - /** - * Whether this tool call was explicitly requested by the user rather than the assistant - */ - isUserRequested?: boolean; - /** - * Tool execution result on success - */ - result?: { - /** - * Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency - */ - content: string; - /** - * Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. - */ - detailedContent?: string; - /** - * Structured content blocks (text, images, audio, resources) returned by the tool in their native format - */ - contents?: ( - | { - /** - * Content block type discriminator - */ - type: "text"; - /** - * The text content - */ - text: string; - } - | { - /** - * Content block type discriminator - */ - type: "terminal"; - /** - * Terminal/shell output text - */ - text: string; - /** - * Process exit code, if the command has completed - */ - exitCode?: number; - /** - * Working directory where the command was executed - */ - cwd?: string; - } - | { - /** - * Content block type discriminator - */ - type: "image"; - /** - * Base64-encoded image data - */ - data: string; - /** - * MIME type of the image (e.g., image/png, image/jpeg) - */ - mimeType: string; - } - | { - /** - * Content block type discriminator - */ - type: "audio"; - /** - * Base64-encoded audio data - */ - data: string; - /** - * MIME type of the audio (e.g., audio/wav, audio/mpeg) - */ - mimeType: string; - } - | { - /** - * Icons associated with this resource - */ - icons?: { - /** - * URL or path to the icon image - */ - src: string; - /** - * MIME type of the icon image - */ - mimeType?: string; - /** - * Available icon sizes (e.g., ['16x16', '32x32']) - */ - sizes?: string[]; - /** - * Theme variant this icon is intended for - */ - theme?: "light" | "dark"; - }[]; - /** - * Resource name identifier - */ - name: string; - /** - * URI identifying the resource - */ - uri: string; - /** - * Human-readable description of the resource - */ - description?: string; - /** - * MIME type of the resource content - */ - mimeType?: string; - /** - * Size of the resource in bytes - */ - size?: number; - /** - * Content block type discriminator - */ - type: "resource_link"; - } - | { - /** - * Content block type discriminator - */ - type: "resource"; - /** - * The embedded resource contents, either text or base64-encoded binary - */ - resource: EmbeddedTextResourceContents | EmbeddedBlobResourceContents; - } - )[]; - }; - /** - * Error details when the tool execution failed - */ - error?: { - /** - * Human-readable error message - */ - message: string; - /** - * Machine-readable error code - */ - code?: string; - }; - /** - * Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) - */ - toolTelemetry?: { - [k: string]: unknown; - }; - /** - * @deprecated - * Tool call ID of the parent tool invocation when this event originates from a sub-agent - */ - parentToolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "skill.invoked"; - /** - * Skill invocation details including content, allowed tools, and plugin metadata - */ - data: { - /** - * Name of the invoked skill - */ - name: string; - /** - * File path to the SKILL.md definition - */ - path: string; - /** - * Full content of the skill file, injected into the conversation for the model - */ - content: string; - /** - * Tool names that should be auto-approved when this skill is active - */ - allowedTools?: string[]; - /** - * Name of the plugin this skill originated from, when applicable - */ - pluginName?: string; - /** - * Version of the plugin this skill originated from, when applicable - */ - pluginVersion?: string; - /** - * Description of the skill from its SKILL.md frontmatter - */ - description?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.started"; - /** - * Sub-agent startup details including parent tool call and agent information - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Description of what the sub-agent does - */ - agentDescription: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.completed"; - /** - * Sub-agent completion details for successful execution - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Model used by the sub-agent - */ - model?: string; - /** - * Total number of tool calls made by the sub-agent - */ - totalToolCalls?: number; - /** - * Total tokens (input + output) consumed by the sub-agent - */ - totalTokens?: number; - /** - * Wall-clock duration of the sub-agent execution in milliseconds - */ - durationMs?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.failed"; - /** - * Sub-agent failure details including error message and agent information - */ - data: { - /** - * Tool call ID of the parent tool invocation that spawned this sub-agent - */ - toolCallId: string; - /** - * Internal name of the sub-agent - */ - agentName: string; - /** - * Human-readable display name of the sub-agent - */ - agentDisplayName: string; - /** - * Error message describing why the sub-agent failed - */ - error: string; - /** - * Model used by the sub-agent (if any model calls succeeded before failure) - */ - model?: string; - /** - * Total number of tool calls made before the sub-agent failed - */ - totalToolCalls?: number; - /** - * Total tokens (input + output) consumed before the sub-agent failed - */ - totalTokens?: number; - /** - * Wall-clock duration of the sub-agent execution in milliseconds - */ - durationMs?: number; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.selected"; - /** - * Custom agent selection details including name and available tools - */ - data: { - /** - * Internal name of the selected custom agent - */ - agentName: string; - /** - * Human-readable display name of the selected custom agent - */ - agentDisplayName: string; - /** - * List of tool names available to this agent, or null for all tools - */ - tools: string[] | null; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "subagent.deselected"; - /** - * Empty payload; the event signals that the custom agent was deselected, returning to the default agent - */ - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "hook.start"; - /** - * Hook invocation start details including type and input data - */ - data: { - /** - * Unique identifier for this hook invocation - */ - hookInvocationId: string; - /** - * Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - */ - hookType: string; - /** - * Input data passed to the hook - */ - input?: { - [k: string]: unknown; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "hook.end"; - /** - * Hook invocation completion details including output, success status, and error information - */ - data: { - /** - * Identifier matching the corresponding hook.start event - */ - hookInvocationId: string; - /** - * Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") - */ - hookType: string; - /** - * Output data produced by the hook - */ - output?: { - [k: string]: unknown; - }; - /** - * Whether the hook completed successfully - */ - success: boolean; - /** - * Error details when the hook failed - */ - error?: { - /** - * Human-readable error message - */ - message: string; - /** - * Error stack trace, when available - */ - stack?: string; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "system.message"; - /** - * System/developer instruction content with role and optional template metadata - */ - data: { - /** - * The system or developer prompt text sent as model input - */ - content: string; - /** - * Message role: "system" for system prompts, "developer" for developer-injected instructions - */ - role: "system" | "developer"; - /** - * Optional name identifier for the message source - */ - name?: string; - /** - * Metadata about the prompt template and its construction - */ - metadata?: { - /** - * Version identifier of the prompt template used - */ - promptVersion?: string; - /** - * Template variables used when constructing the prompt - */ - variables?: { - [k: string]: unknown; - }; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - /** - * When true, the event is transient and not persisted to the session event log on disk - */ - ephemeral?: boolean; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "system.notification"; - /** - * System-generated notification for runtime events like background task completion - */ - data: { - /** - * The notification text, typically wrapped in XML tags - */ - content: string; - /** - * Structured metadata identifying what triggered this notification - */ - kind: - | { - type: "agent_completed"; - /** - * Unique identifier of the background agent - */ - agentId: string; - /** - * Type of the agent (e.g., explore, task, general-purpose) - */ - agentType: string; - /** - * Whether the agent completed successfully or failed - */ - status: "completed" | "failed"; - /** - * Human-readable description of the agent task - */ - description?: string; - /** - * The full prompt given to the background agent - */ - prompt?: string; - } - | { - type: "agent_idle"; - /** - * Unique identifier of the background agent - */ - agentId: string; - /** - * Type of the agent (e.g., explore, task, general-purpose) - */ - agentType: string; - /** - * Human-readable description of the agent task - */ - description?: string; - } - | { - type: "shell_completed"; - /** - * Unique identifier of the shell session - */ - shellId: string; - /** - * Exit code of the shell command, if available - */ - exitCode?: number; - /** - * Human-readable description of the command - */ - description?: string; - } - | { - type: "shell_detached_completed"; - /** - * Unique identifier of the detached shell session - */ - shellId: string; - /** - * Human-readable description of the command - */ - description?: string; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "permission.requested"; - /** - * Permission request notification requiring client approval with request details - */ - data: { - /** - * Unique identifier for this permission request; used to respond via session.respondToPermission() - */ - requestId: string; - /** - * Details of the permission being requested - */ - permissionRequest: - | { - /** - * Permission kind discriminator - */ - kind: "shell"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * The complete shell command text to be executed - */ - fullCommandText: string; - /** - * Human-readable description of what the command intends to do - */ - intention: string; - /** - * Parsed command identifiers found in the command text - */ - commands: { - /** - * Command identifier (e.g., executable name) - */ - identifier: string; - /** - * Whether this command is read-only (no side effects) - */ - readOnly: boolean; - }[]; - /** - * File paths that may be read or written by the command - */ - possiblePaths: string[]; - /** - * URLs that may be accessed by the command - */ - possibleUrls: { - /** - * URL that may be accessed by the command - */ - url: string; - }[]; - /** - * Whether the command includes a file write redirection (e.g., > or >>) - */ - hasWriteFileRedirection: boolean; - /** - * Whether the UI can offer session-wide approval for this command pattern - */ - canOfferSessionApproval: boolean; - /** - * Optional warning message about risks of running this command - */ - warning?: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "write"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of the intended file change - */ - intention: string; - /** - * Path of the file being written to - */ - fileName: string; - /** - * Unified diff showing the proposed changes - */ - diff: string; - /** - * Complete new file contents for newly created files - */ - newFileContents?: string; - /** - * Whether the UI can offer session-wide approval for file write operations - */ - canOfferSessionApproval: boolean; - } - | { - /** - * Permission kind discriminator - */ - kind: "read"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of why the file is being read - */ - intention: string; - /** - * Path of the file or directory being read - */ - path: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "mcp"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the MCP server providing the tool - */ - serverName: string; - /** - * Internal name of the MCP tool - */ - toolName: string; - /** - * Human-readable title of the MCP tool - */ - toolTitle: string; - /** - * Arguments to pass to the MCP tool - */ - args?: { - [k: string]: unknown; - }; - /** - * Whether this MCP tool is read-only (no side effects) - */ - readOnly: boolean; - } - | { - /** - * Permission kind discriminator - */ - kind: "url"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Human-readable description of why the URL is being accessed - */ - intention: string; - /** - * URL to be fetched - */ - url: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "memory"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Whether this is a store or vote memory operation - */ - action?: "store" | "vote"; - /** - * Topic or subject of the memory (store only) - */ - subject?: string; - /** - * The fact being stored or voted on - */ - fact: string; - /** - * Source references for the stored fact (store only) - */ - citations?: string; - /** - * Vote direction (vote only) - */ - direction?: "upvote" | "downvote"; - /** - * Reason for the vote (vote only) - */ - reason?: string; - } - | { - /** - * Permission kind discriminator - */ - kind: "custom-tool"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the custom tool - */ - toolName: string; - /** - * Description of what the custom tool does - */ - toolDescription: string; - /** - * Arguments to pass to the custom tool - */ - args?: { - [k: string]: unknown; - }; - } - | { - /** - * Permission kind discriminator - */ - kind: "hook"; - /** - * Tool call ID that triggered this permission request - */ - toolCallId?: string; - /** - * Name of the tool the hook is gating - */ - toolName: string; - /** - * Arguments of the tool call being gated - */ - toolArgs?: { - [k: string]: unknown; - }; - /** - * Optional message from the hook explaining why confirmation is needed - */ - hookMessage?: string; - }; - /** - * When true, this permission was already resolved by a permissionRequest hook and requires no client action - */ - resolvedByHook?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "permission.completed"; - /** - * Permission request completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved permission request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The result of the permission request - */ - result: { - /** - * The outcome of the permission request - */ - kind: - | "approved" - | "denied-by-rules" - | "denied-no-approval-rule-and-could-not-request-from-user" - | "denied-interactively-by-user" - | "denied-by-content-exclusion-policy" - | "denied-by-permission-request-hook"; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user_input.requested"; - /** - * User input request notification with question and optional predefined choices - */ - data: { - /** - * Unique identifier for this input request; used to respond via session.respondToUserInput() - */ - requestId: string; - /** - * The question or prompt to present to the user - */ - question: string; - /** - * Predefined choices for the user to select from, if applicable - */ - choices?: string[]; - /** - * Whether the user can provide a free-form text response in addition to predefined choices - */ - allowFreeform?: boolean; - /** - * The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses - */ - toolCallId?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "user_input.completed"; - /** - * User input request completion with the user's response - */ - data: { - /** - * Request ID of the resolved user input request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The user's answer to the input request - */ - answer?: string; - /** - * Whether the answer was typed as free-form text rather than selected from choices - */ - wasFreeform?: boolean; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "elicitation.requested"; - /** - * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) - */ - data: { - /** - * Unique identifier for this elicitation request; used to respond via session.respondToElicitation() - */ - requestId: string; - /** - * Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs - */ - toolCallId?: string; - /** - * The source that initiated the request (MCP server name, or absent for agent-initiated) - */ - elicitationSource?: string; - /** - * Message describing what information is needed from the user - */ - message: string; - /** - * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. - */ - mode?: "form" | "url"; - /** - * JSON Schema describing the form fields to present to the user (form mode only) - */ - requestedSchema?: { - /** - * Schema type indicator (always 'object') - */ - type: "object"; - /** - * Form field definitions, keyed by field name - */ - properties: { - [k: string]: unknown; - }; - /** - * List of required field names - */ - required?: string[]; - }; - /** - * URL to open in the user's browser (url mode only) - */ - url?: string; - [k: string]: unknown; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "elicitation.completed"; - /** - * Elicitation request completion with the user's response - */ - data: { - /** - * Request ID of the resolved elicitation request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) - */ - action?: "accept" | "decline" | "cancel"; - /** - * The submitted form data when action is 'accept'; keys match the requested schema fields - */ - content?: { - [k: string]: string | number | boolean | string[]; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "sampling.requested"; - /** - * Sampling request from an MCP server; contains the server name and a requestId for correlation - */ - data: { - /** - * Unique identifier for this sampling request; used to respond via session.respondToSampling() - */ - requestId: string; - /** - * Name of the MCP server that initiated the sampling request - */ - serverName: string; - /** - * The JSON-RPC request ID from the MCP protocol - */ - mcpRequestId: string | number; - [k: string]: unknown; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "sampling.completed"; - /** - * Sampling request completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved sampling request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "mcp.oauth_required"; - /** - * OAuth authentication request for an MCP server - */ - data: { - /** - * Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() - */ - requestId: string; - /** - * Display name of the MCP server that requires OAuth - */ - serverName: string; - /** - * URL of the MCP server that requires OAuth - */ - serverUrl: string; - /** - * Static OAuth client configuration, if the server specifies one - */ - staticClientConfig?: { - /** - * OAuth client ID for the server - */ - clientId: string; - /** - * Whether this is a public OAuth client - */ - publicClient?: boolean; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "mcp.oauth_completed"; - /** - * MCP OAuth request completion notification - */ - data: { - /** - * Request ID of the resolved OAuth request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "external_tool.requested"; - /** - * External tool invocation request for client-side tool execution - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToExternalTool() - */ - requestId: string; - /** - * Session ID that this external tool request belongs to - */ - sessionId: string; - /** - * Tool call ID assigned to this external tool invocation - */ - toolCallId: string; - /** - * Name of the external tool to invoke - */ - toolName: string; - /** - * Arguments to pass to the external tool - */ - arguments?: { - [k: string]: unknown; - }; - /** - * W3C Trace Context traceparent header for the execute_tool span - */ - traceparent?: string; - /** - * W3C Trace Context tracestate header for the execute_tool span - */ - tracestate?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "external_tool.completed"; - /** - * External tool completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved external tool request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.queued"; - /** - * Queued slash command dispatch request for client execution - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToQueuedCommand() - */ - requestId: string; - /** - * The slash command text to be executed (e.g., /help, /clear) - */ - command: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.execute"; - /** - * Registered command dispatch request routed to the owning client - */ - data: { - /** - * Unique identifier; used to respond via session.commands.handlePendingCommand() - */ - requestId: string; - /** - * The full command text (e.g., /deploy production) - */ - command: string; - /** - * Command name without leading / - */ - commandName: string; - /** - * Raw argument string after the command name - */ - args: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "command.completed"; - /** - * Queued command completion notification signaling UI dismissal - */ - data: { - /** - * Request ID of the resolved command request; clients should dismiss any UI for this request - */ - requestId: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "commands.changed"; - /** - * SDK command registration change notification - */ - data: { - /** - * Current list of registered SDK commands - */ - commands: { - name: string; - description?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "capabilities.changed"; - /** - * Session capability change notification - */ - data: { - /** - * UI capability changes - */ - ui?: { - /** - * Whether elicitation is now supported - */ - elicitation?: boolean; - }; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "exit_plan_mode.requested"; - /** - * Plan approval request with plan content and available user actions - */ - data: { - /** - * Unique identifier for this request; used to respond via session.respondToExitPlanMode() - */ - requestId: string; - /** - * Summary of the plan that was created - */ - summary: string; - /** - * Full content of the plan file - */ - planContent: string; - /** - * Available actions the user can take (e.g., approve, edit, reject) - */ - actions: string[]; - /** - * The recommended action for the user to take - */ - recommendedAction: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "exit_plan_mode.completed"; - /** - * Plan mode exit completion with the user's approval decision and optional feedback - */ - data: { - /** - * Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request - */ - requestId: string; - /** - * Whether the plan was approved by the user - */ - approved?: boolean; - /** - * Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') - */ - selectedAction?: string; - /** - * Whether edits should be auto-approved without confirmation - */ - autoApproveEdits?: boolean; - /** - * Free-form feedback from the user if they requested changes to the plan - */ - feedback?: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.tools_updated"; - data: { - model: string; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.background_tasks_changed"; - data: {}; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.skills_loaded"; - data: { - /** - * Array of resolved skill metadata - */ - skills: { - /** - * Unique identifier for the skill - */ - name: string; - /** - * Description of what the skill does - */ - description: string; - /** - * Source location type of the skill (e.g., project, personal, plugin) - */ - source: string; - /** - * Whether the skill can be invoked by the user as a slash command - */ - userInvocable: boolean; - /** - * Whether the skill is currently enabled - */ - enabled: boolean; - /** - * Absolute path to the skill file, if available - */ - path?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.custom_agents_updated"; - data: { - /** - * Array of loaded custom agent metadata - */ - agents: { - /** - * Unique identifier for the agent - */ - id: string; - /** - * Internal name of the agent - */ - name: string; - /** - * Human-readable display name - */ - displayName: string; - /** - * Description of what the agent does - */ - description: string; - /** - * Source location: user, project, inherited, remote, or plugin - */ - source: string; - /** - * List of tool names available to this agent - */ - tools: string[]; - /** - * Whether the agent can be selected by the user - */ - userInvocable: boolean; - /** - * Model override for this agent, if set - */ - model?: string; - }[]; - /** - * Non-fatal warnings from agent loading - */ - warnings: string[]; - /** - * Fatal errors from agent loading - */ - errors: string[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mcp_servers_loaded"; - data: { - /** - * Array of MCP server status summaries - */ - servers: { - /** - * Server name (config key) - */ - name: string; - /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: string; - /** - * Error message if the server failed to connect - */ - error?: string; - }[]; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.mcp_server_status_changed"; - data: { - /** - * Name of the MCP server whose status changed - */ - serverName: string; - /** - * New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - */ - status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; - }; - } - | { - /** - * Unique event identifier (UUID v4), generated when the event is emitted - */ - id: string; - /** - * ISO 8601 timestamp when the event was created - */ - timestamp: string; - /** - * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. - */ - parentId: string | null; - ephemeral: true; - /** - * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. - */ - agentId?: string; - type: "session.extensions_loaded"; - data: { - /** - * Array of discovered extensions and their status - */ - extensions: { - /** - * Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') - */ - id: string; - /** - * Extension name (directory name) - */ - name: string; - /** - * Discovery source - */ - source: "project" | "user"; - /** - * Current status: running, disabled, failed, or starting - */ - status: "running" | "disabled" | "failed" | "starting"; - }[]; - }; - }; + | StartEvent + | ResumeEvent + | RemoteSteerableChangedEvent + | ErrorEvent + | IdleEvent + | TitleChangedEvent + | InfoEvent + | WarningEvent + | ModelChangeEvent + | ModeChangedEvent + | PlanChangedEvent + | WorkspaceFileChangedEvent + | HandoffEvent + | TruncationEvent + | SnapshotRewindEvent + | ShutdownEvent + | ContextChangedEvent + | UsageInfoEvent + | CompactionStartEvent + | CompactionCompleteEvent + | TaskCompleteEvent + | UserMessageEvent + | PendingMessagesModifiedEvent + | AssistantTurnStartEvent + | AssistantIntentEvent + | AssistantReasoningEvent + | AssistantReasoningDeltaEvent + | AssistantStreamingDeltaEvent + | AssistantMessageEvent + | AssistantMessageDeltaEvent + | AssistantTurnEndEvent + | AssistantUsageEvent + | AbortEvent + | ToolUserRequestedEvent + | ToolExecutionStartEvent + | ToolExecutionPartialResultEvent + | ToolExecutionProgressEvent + | ToolExecutionCompleteEvent + | SkillInvokedEvent + | SubagentStartedEvent + | SubagentCompletedEvent + | SubagentFailedEvent + | SubagentSelectedEvent + | SubagentDeselectedEvent + | HookStartEvent + | HookEndEvent + | SystemMessageEvent + | SystemNotificationEvent + | PermissionRequestedEvent + | PermissionCompletedEvent + | UserInputRequestedEvent + | UserInputCompletedEvent + | ElicitationRequestedEvent + | ElicitationCompletedEvent + | SamplingRequestedEvent + | SamplingCompletedEvent + | McpOauthRequiredEvent + | McpOauthCompletedEvent + | ExternalToolRequestedEvent + | ExternalToolCompletedEvent + | CommandQueuedEvent + | CommandExecuteEvent + | CommandCompletedEvent + | CommandsChangedEvent + | CapabilitiesChangedEvent + | ExitPlanModeRequestedEvent + | ExitPlanModeCompletedEvent + | ToolsUpdatedEvent + | BackgroundTasksChangedEvent + | SkillsLoadedEvent + | CustomAgentsUpdatedEvent + | McpServersLoadedEvent + | McpServerStatusChangedEvent + | ExtensionsLoadedEvent; +export interface StartEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.start"; + data: StartData; +} +/** + * Session initialization metadata including context and configuration + */ +export interface StartData { + /** + * Unique identifier for the session + */ + sessionId: string; + /** + * Schema version number for the session event format + */ + version: number; + /** + * Identifier of the software producing the events (e.g., "copilot-agent") + */ + producer: string; + /** + * Version string of the Copilot application + */ + copilotVersion: string; + /** + * ISO 8601 timestamp when the session was created + */ + startTime: string; + /** + * Model selected at session creation time, if any + */ + selectedModel?: string; + /** + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + */ + reasoningEffort?: string; + context?: WorkingDirectoryContext; + /** + * Whether the session was already in use by another client at start time + */ + alreadyInUse?: boolean; + /** + * Whether this session supports remote steering via Mission Control + */ + remoteSteerable?: boolean; +} /** * Working directory and git context at session start */ @@ -3986,31 +182,4101 @@ export interface WorkingDirectoryContext { */ baseCommit?: string; } -export interface EmbeddedTextResourceContents { +export interface ResumeEvent { /** - * URI identifying the resource + * Unique event identifier (UUID v4), generated when the event is emitted */ - uri: string; + id: string; /** - * MIME type of the text content + * ISO 8601 timestamp when the event was created */ - mimeType?: string; + timestamp: string; /** - * Text content of the resource + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. */ - text: string; + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.resume"; + data: ResumeData; } -export interface EmbeddedBlobResourceContents { +/** + * Session resume metadata including current context and event count + */ +export interface ResumeData { /** - * URI identifying the resource + * ISO 8601 timestamp when the session was resumed */ - uri: string; + resumeTime: string; /** - * MIME type of the blob content + * Total number of persisted events in the session at the time of resume */ - mimeType?: string; + eventCount: number; /** - * Base64-encoded binary content of the resource + * Model currently selected at resume time */ - blob: string; + selectedModel?: string; + /** + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + */ + reasoningEffort?: string; + context?: WorkingDirectoryContext; + /** + * Whether the session was already in use by another client at resume time + */ + alreadyInUse?: boolean; + /** + * Whether this session supports remote steering via Mission Control + */ + remoteSteerable?: boolean; +} +export interface RemoteSteerableChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.remote_steerable_changed"; + data: RemoteSteerableChangedData; +} +/** + * Notifies Mission Control that the session's remote steering capability has changed + */ +export interface RemoteSteerableChangedData { + /** + * Whether this session now supports remote steering via Mission Control + */ + remoteSteerable: boolean; +} +export interface ErrorEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.error"; + data: ErrorData; +} +/** + * Error details for timeline display including message and optional diagnostic information + */ +export interface ErrorData { + /** + * Category of error (e.g., "authentication", "authorization", "quota", "rate_limit", "context_limit", "query") + */ + errorType: string; + /** + * Human-readable error message + */ + message: string; + /** + * Error stack trace, when available + */ + stack?: string; + /** + * HTTP status code from the upstream request, if applicable + */ + statusCode?: number; + /** + * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + */ + providerCallId?: string; + /** + * Optional URL associated with this error that the user can open in a browser + */ + url?: string; +} +export interface IdleEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.idle"; + data: IdleData; +} +/** + * Payload indicating the session is idle with no background agents in flight + */ +export interface IdleData { + /** + * True when the preceding agentic loop was cancelled via abort signal + */ + aborted?: boolean; +} +export interface TitleChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.title_changed"; + data: TitleChangedData; +} +/** + * Session title change payload containing the new display title + */ +export interface TitleChangedData {} +export interface InfoEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.info"; + data: InfoData; +} +/** + * Informational message for timeline display with categorization + */ +export interface InfoData { + /** + * Category of informational message (e.g., "notification", "timing", "context_window", "mcp", "snapshot", "configuration", "authentication", "model") + */ + infoType: string; + /** + * Human-readable informational message for display in the timeline + */ + message: string; + /** + * Optional URL associated with this message that the user can open in a browser + */ + url?: string; +} +export interface WarningEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.warning"; + data: WarningData; +} +/** + * Warning message for timeline display with categorization + */ +export interface WarningData { + /** + * Category of warning (e.g., "subscription", "policy", "mcp") + */ + warningType: string; + /** + * Human-readable warning message for display in the timeline + */ + message: string; + /** + * Optional URL associated with this warning that the user can open in a browser + */ + url?: string; +} +export interface ModelChangeEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.model_change"; + data: ModelChangeData; +} +/** + * Model change details including previous and new model identifiers + */ +export interface ModelChangeData { + /** + * Model that was previously selected, if any + */ + previousModel?: string; + /** + * Newly selected model identifier + */ + newModel: string; + /** + * Reasoning effort level before the model change, if applicable + */ + previousReasoningEffort?: string; + /** + * Reasoning effort level after the model change, if applicable + */ + reasoningEffort?: string; +} +export interface ModeChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.mode_changed"; + data: ModeChangedData; +} +/** + * Agent mode change details including previous and new modes + */ +export interface ModeChangedData { + /** + * Agent mode before the change (e.g., "interactive", "plan", "autopilot") + */ + previousMode: string; + /** + * Agent mode after the change (e.g., "interactive", "plan", "autopilot") + */ + newMode: string; +} +export interface PlanChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.plan_changed"; + data: PlanChangedData; +} +/** + * Plan file operation details indicating what changed + */ +export interface PlanChangedData { + /** + * The type of operation performed on the plan file + */ + operation: "create" | "update" | "delete"; +} +export interface WorkspaceFileChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.workspace_file_changed"; + data: WorkspaceFileChangedData; +} +/** + * Workspace file change details including path and operation type + */ +export interface WorkspaceFileChangedData { + /** + * Relative path within the session workspace files directory + */ + path: string; + /** + * Whether the file was newly created or updated + */ + operation: "create" | "update"; +} +export interface HandoffEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.handoff"; + data: HandoffData; +} +/** + * Session handoff metadata including source, context, and repository information + */ +export interface HandoffData { + /** + * ISO 8601 timestamp when the handoff occurred + */ + handoffTime: string; + /** + * Origin type of the session being handed off + */ + sourceType: "remote" | "local"; + repository?: HandoffRepository; + /** + * Additional context information for the handoff + */ + context?: string; + /** + * Summary of the work done in the source session + */ + summary?: string; + /** + * Session ID of the remote session being handed off + */ + remoteSessionId?: string; + /** + * GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com) + */ + host?: string; +} +/** + * Repository context for the handed-off session + */ +export interface HandoffRepository { + /** + * Repository owner (user or organization) + */ + owner: string; + /** + * Repository name + */ + name: string; + /** + * Git branch name, if applicable + */ + branch?: string; +} +export interface TruncationEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.truncation"; + data: TruncationData; +} +/** + * Conversation truncation statistics including token counts and removed content metrics + */ +export interface TruncationData { + /** + * Maximum token count for the model's context window + */ + tokenLimit: number; + /** + * Total tokens in conversation messages before truncation + */ + preTruncationTokensInMessages: number; + /** + * Number of conversation messages before truncation + */ + preTruncationMessagesLength: number; + /** + * Total tokens in conversation messages after truncation + */ + postTruncationTokensInMessages: number; + /** + * Number of conversation messages after truncation + */ + postTruncationMessagesLength: number; + /** + * Number of tokens removed by truncation + */ + tokensRemovedDuringTruncation: number; + /** + * Number of messages removed by truncation + */ + messagesRemovedDuringTruncation: number; + /** + * Identifier of the component that performed truncation (e.g., "BasicTruncator") + */ + performedBy: string; +} +export interface SnapshotRewindEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.snapshot_rewind"; + data: SnapshotRewindData; +} +/** + * Session rewind details including target event and count of removed events + */ +export interface SnapshotRewindData { + /** + * Event ID that was rewound to; this event and all after it were removed + */ + upToEventId: string; + /** + * Number of events that were removed by the rewind + */ + eventsRemoved: number; +} +export interface ShutdownEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.shutdown"; + data: ShutdownData; +} +/** + * Session termination metrics including usage statistics, code changes, and shutdown reason + */ +export interface ShutdownData { + /** + * Whether the session ended normally ("routine") or due to a crash/fatal error ("error") + */ + shutdownType: "routine" | "error"; + /** + * Error description when shutdownType is "error" + */ + errorReason?: string; + /** + * Total number of premium API requests used during the session + */ + totalPremiumRequests: number; + /** + * Cumulative time spent in API calls during the session, in milliseconds + */ + totalApiDurationMs: number; + /** + * Unix timestamp (milliseconds) when the session started + */ + sessionStartTime: number; + codeChanges: ShutdownCodeChanges; + /** + * Per-model usage breakdown, keyed by model identifier + */ + modelMetrics: { + [k: string]: ShutdownModelMetric; + }; + /** + * Model that was selected at the time of shutdown + */ + currentModel?: string; + /** + * Total tokens in context window at shutdown + */ + currentTokens?: number; + /** + * System message token count at shutdown + */ + systemTokens?: number; + /** + * Non-system message token count at shutdown + */ + conversationTokens?: number; + /** + * Tool definitions token count at shutdown + */ + toolDefinitionsTokens?: number; +} +/** + * Aggregate code change metrics for the session + */ +export interface ShutdownCodeChanges { + /** + * Total number of lines added during the session + */ + linesAdded: number; + /** + * Total number of lines removed during the session + */ + linesRemoved: number; + /** + * List of file paths that were modified during the session + */ + filesModified: string[]; +} +export interface ShutdownModelMetric { + requests: ShutdownModelMetricRequests; + usage: ShutdownModelMetricUsage; +} +/** + * Request count and cost metrics + */ +export interface ShutdownModelMetricRequests { + /** + * Total number of API requests made to this model + */ + count: number; + /** + * Cumulative cost multiplier for requests to this model + */ + cost: number; +} +/** + * Token usage breakdown + */ +export interface ShutdownModelMetricUsage { + /** + * Total input tokens consumed across all requests to this model + */ + inputTokens: number; + /** + * Total output tokens produced across all requests to this model + */ + outputTokens: number; + /** + * Total tokens read from prompt cache across all requests + */ + cacheReadTokens: number; + /** + * Total tokens written to prompt cache across all requests + */ + cacheWriteTokens: number; + /** + * Total reasoning tokens produced across all requests to this model + */ + reasoningTokens?: number; +} +export interface ContextChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.context_changed"; + data: WorkingDirectoryContext; +} +export interface UsageInfoEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.usage_info"; + data: UsageInfoData; +} +/** + * Current context window usage statistics including token and message counts + */ +export interface UsageInfoData { + /** + * Maximum token count for the model's context window + */ + tokenLimit: number; + /** + * Current number of tokens in the context window + */ + currentTokens: number; + /** + * Current number of messages in the conversation + */ + messagesLength: number; + /** + * Token count from system message(s) + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) + */ + conversationTokens?: number; + /** + * Token count from tool definitions + */ + toolDefinitionsTokens?: number; + /** + * Whether this is the first usage_info event emitted in this session + */ + isInitial?: boolean; +} +export interface CompactionStartEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.compaction_start"; + data: CompactionStartData; +} +/** + * Context window breakdown at the start of LLM-powered conversation compaction + */ +export interface CompactionStartData { + /** + * Token count from system message(s) at compaction start + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) at compaction start + */ + conversationTokens?: number; + /** + * Token count from tool definitions at compaction start + */ + toolDefinitionsTokens?: number; +} +export interface CompactionCompleteEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.compaction_complete"; + data: CompactionCompleteData; +} +/** + * Conversation compaction results including success status, metrics, and optional error details + */ +export interface CompactionCompleteData { + /** + * Whether compaction completed successfully + */ + success: boolean; + /** + * Error message if compaction failed + */ + error?: string; + /** + * Total tokens in conversation before compaction + */ + preCompactionTokens?: number; + /** + * Total tokens in conversation after compaction + */ + postCompactionTokens?: number; + /** + * Number of messages before compaction + */ + preCompactionMessagesLength?: number; + /** + * Number of messages removed during compaction + */ + messagesRemoved?: number; + /** + * Number of tokens removed during compaction + */ + tokensRemoved?: number; + /** + * LLM-generated summary of the compacted conversation history + */ + summaryContent?: string; + /** + * Checkpoint snapshot number created for recovery + */ + checkpointNumber?: number; + /** + * File path where the checkpoint was stored + */ + checkpointPath?: string; + compactionTokensUsed?: CompactionCompleteCompactionTokensUsed; + /** + * GitHub request tracing ID (x-github-request-id header) for the compaction LLM call + */ + requestId?: string; + /** + * Token count from system message(s) after compaction + */ + systemTokens?: number; + /** + * Token count from non-system messages (user, assistant, tool) after compaction + */ + conversationTokens?: number; + /** + * Token count from tool definitions after compaction + */ + toolDefinitionsTokens?: number; +} +/** + * Token usage breakdown for the compaction LLM call + */ +export interface CompactionCompleteCompactionTokensUsed { + /** + * Input tokens consumed by the compaction LLM call + */ + input: number; + /** + * Output tokens produced by the compaction LLM call + */ + output: number; + /** + * Cached input tokens reused in the compaction LLM call + */ + cachedInput: number; +} +export interface TaskCompleteEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.task_complete"; + data: TaskCompleteData; +} +/** + * Task completion notification with summary from the agent + */ +export interface TaskCompleteData { + /** + * Summary of the completed task, provided by the agent + */ + summary?: string; + /** + * Whether the tool call succeeded. False when validation failed (e.g., invalid arguments) + */ + success?: boolean; +} +export interface UserMessageEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "user.message"; + data: UserMessageData; +} +export interface UserMessageData { + /** + * The user's message text as displayed in the timeline + */ + content: string; + /** + * Transformed version of the message sent to the model, with XML wrapping, timestamps, and other augmentations for prompt caching + */ + transformedContent?: string; + /** + * Files, selections, or GitHub references attached to the message + */ + attachments?: ( + | UserMessageAttachmentFile + | UserMessageAttachmentDirectory + | UserMessageAttachmentSelection + | UserMessageAttachmentGithubReference + | UserMessageAttachmentBlob + )[]; + /** + * Normalized document MIME types that were sent natively instead of through tagged_files XML + */ + supportedNativeDocumentMimeTypes?: string[]; + /** + * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit + */ + nativeDocumentPathFallbackPaths?: string[]; + /** + * 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; + /** + * The agent mode that was active when this message was sent + */ + agentMode?: "interactive" | "plan" | "autopilot" | "shell"; + /** + * CAPI interaction ID for correlating this user message with its turn + */ + interactionId?: string; +} +/** + * File attachment + */ +export interface UserMessageAttachmentFile { + /** + * Attachment type discriminator + */ + type: "file"; + /** + * Absolute file path + */ + path: string; + /** + * User-facing display name for the attachment + */ + displayName: string; + lineRange?: UserMessageAttachmentFileLineRange; +} +/** + * Optional line range to scope the attachment to a specific section of the file + */ +export interface UserMessageAttachmentFileLineRange { + /** + * Start line number (1-based) + */ + start: number; + /** + * End line number (1-based, inclusive) + */ + end: number; +} +/** + * Directory attachment + */ +export interface UserMessageAttachmentDirectory { + /** + * Attachment type discriminator + */ + type: "directory"; + /** + * Absolute directory path + */ + path: string; + /** + * User-facing display name for the attachment + */ + displayName: string; +} +/** + * Code selection attachment from an editor + */ +export interface UserMessageAttachmentSelection { + /** + * Attachment type discriminator + */ + type: "selection"; + /** + * Absolute path to the file containing the selection + */ + filePath: string; + /** + * User-facing display name for the selection + */ + displayName: string; + /** + * The selected text content + */ + text: string; + selection: UserMessageAttachmentSelectionDetails; +} +/** + * Position range of the selection within the file + */ +export interface UserMessageAttachmentSelectionDetails { + start: UserMessageAttachmentSelectionDetailsStart; + end: UserMessageAttachmentSelectionDetailsEnd; +} +/** + * Start position of the selection + */ +export interface UserMessageAttachmentSelectionDetailsStart { + /** + * Start line number (0-based) + */ + line: number; + /** + * Start character offset within the line (0-based) + */ + character: number; +} +/** + * End position of the selection + */ +export interface UserMessageAttachmentSelectionDetailsEnd { + /** + * End line number (0-based) + */ + line: number; + /** + * End character offset within the line (0-based) + */ + character: number; +} +/** + * GitHub issue, pull request, or discussion reference + */ +export interface UserMessageAttachmentGithubReference { + /** + * Attachment type discriminator + */ + type: "github_reference"; + /** + * Issue, pull request, or discussion number + */ + number: number; + /** + * Type of GitHub reference + */ + referenceType: "issue" | "pr" | "discussion"; + /** + * Current state of the referenced item (e.g., open, closed, merged) + */ + state: string; + /** + * URL to the referenced item on GitHub + */ + url: string; +} +/** + * Blob attachment with inline base64-encoded data + */ +export interface UserMessageAttachmentBlob { + /** + * Attachment type discriminator + */ + type: "blob"; + /** + * Base64-encoded content + */ + data: string; + /** + * MIME type of the inline data + */ + mimeType: string; + /** + * User-facing display name for the attachment + */ + displayName?: string; +} +export interface PendingMessagesModifiedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "pending_messages.modified"; + data: PendingMessagesModifiedData; +} +/** + * Empty payload; the event signals that the pending message queue has changed + */ +export interface PendingMessagesModifiedData {} +export interface AssistantTurnStartEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.turn_start"; + data: AssistantTurnStartData; +} +/** + * Turn initialization metadata including identifier and interaction tracking + */ +export interface AssistantTurnStartData { + /** + * Identifier for this turn within the agentic loop, typically a stringified turn number + */ + turnId: string; + /** + * CAPI interaction ID for correlating this turn with upstream telemetry + */ + interactionId?: string; +} +export interface AssistantIntentEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.intent"; + data: AssistantIntentData; +} +/** + * Agent intent description for current activity or plan + */ +export interface AssistantIntentData { + /** + * Short description of what the agent is currently doing or planning to do + */ + intent: string; +} +export interface AssistantReasoningEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.reasoning"; + data: AssistantReasoningData; +} +/** + * Assistant reasoning content for timeline display with complete thinking text + */ +export interface AssistantReasoningData { + /** + * Unique identifier for this reasoning block + */ + reasoningId: string; + /** + * The complete extended thinking text from the model + */ + content: string; +} +export interface AssistantReasoningDeltaEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.reasoning_delta"; + data: AssistantReasoningDeltaData; +} +/** + * Streaming reasoning delta for incremental extended thinking updates + */ +export interface AssistantReasoningDeltaData { + /** + * Reasoning block ID this delta belongs to, matching the corresponding assistant.reasoning event + */ + reasoningId: string; + /** + * Incremental text chunk to append to the reasoning content + */ + deltaContent: string; +} +export interface AssistantStreamingDeltaEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.streaming_delta"; + data: AssistantStreamingDeltaData; +} +/** + * Streaming response progress with cumulative byte count + */ +export interface AssistantStreamingDeltaData { + /** + * Cumulative total bytes received from the streaming response so far + */ + totalResponseSizeBytes: number; +} +export interface AssistantMessageEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.message"; + data: AssistantMessageData; +} +/** + * Assistant response containing text content, optional tool requests, and interaction metadata + */ +export interface AssistantMessageData { + /** + * Unique identifier for this assistant message + */ + messageId: string; + /** + * The assistant's text response content + */ + content: string; + /** + * Tool invocations requested by the assistant in this message + */ + toolRequests?: AssistantMessageToolRequest[]; + /** + * Opaque/encrypted extended thinking data from Anthropic models. Session-bound and stripped on resume. + */ + reasoningOpaque?: string; + /** + * Readable reasoning text from the model's extended thinking + */ + reasoningText?: string; + /** + * Encrypted reasoning content from OpenAI models. Session-bound and stripped on resume. + */ + encryptedContent?: string; + /** + * Generation phase for phased-output models (e.g., thinking vs. response phases) + */ + phase?: string; + /** + * Actual output token count from the API response (completion_tokens), used for accurate token accounting + */ + outputTokens?: number; + /** + * CAPI interaction ID for correlating this message with upstream telemetry + */ + interactionId?: string; + /** + * GitHub request tracing ID (x-github-request-id header) for correlating with server-side logs + */ + requestId?: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; +} +/** + * A tool invocation request from the assistant + */ +export interface AssistantMessageToolRequest { + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Name of the tool being invoked + */ + name: string; + /** + * Arguments to pass to the tool, format depends on the tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent. + */ + type?: "function" | "custom"; + /** + * Human-readable display title for the tool + */ + toolTitle?: string; + /** + * Name of the MCP server hosting this tool, when the tool is an MCP tool + */ + mcpServerName?: string; + /** + * Resolved intention summary describing what this specific call does + */ + intentionSummary?: string | null; +} +export interface AssistantMessageDeltaEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.message_delta"; + data: AssistantMessageDeltaData; +} +/** + * Streaming assistant message delta for incremental response updates + */ +export interface AssistantMessageDeltaData { + /** + * Message ID this delta belongs to, matching the corresponding assistant.message event + */ + messageId: string; + /** + * Incremental text chunk to append to the message content + */ + deltaContent: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; +} +export interface AssistantTurnEndEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.turn_end"; + data: AssistantTurnEndData; +} +/** + * Turn completion metadata including the turn identifier + */ +export interface AssistantTurnEndData { + /** + * Identifier of the turn that has ended, matching the corresponding assistant.turn_start event + */ + turnId: string; +} +export interface AssistantUsageEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "assistant.usage"; + data: AssistantUsageData; +} +/** + * LLM API call usage metrics including tokens, costs, quotas, and billing information + */ +export interface AssistantUsageData { + /** + * Model identifier used for this API call + */ + model: string; + /** + * Number of input tokens consumed + */ + inputTokens?: number; + /** + * Number of output tokens produced + */ + outputTokens?: number; + /** + * Number of tokens read from prompt cache + */ + cacheReadTokens?: number; + /** + * Number of tokens written to prompt cache + */ + cacheWriteTokens?: number; + /** + * Number of output tokens used for reasoning (e.g., chain-of-thought) + */ + reasoningTokens?: number; + /** + * Model multiplier cost for billing purposes + */ + cost?: number; + /** + * Duration of the API call in milliseconds + */ + duration?: number; + /** + * Time to first token in milliseconds. Only available for streaming requests + */ + ttftMs?: number; + /** + * Average inter-token latency in milliseconds. Only available for streaming requests + */ + interTokenLatencyMs?: number; + /** + * What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls + */ + initiator?: string; + /** + * Completion ID from the model provider (e.g., chatcmpl-abc123) + */ + apiCallId?: string; + /** + * GitHub request tracing ID (x-github-request-id header) for server-side log correlation + */ + providerCallId?: string; + /** + * @deprecated + * Parent tool call ID when this usage originates from a sub-agent + */ + parentToolCallId?: string; + /** + * Per-quota resource usage snapshots, keyed by quota identifier + */ + quotaSnapshots?: { + [k: string]: AssistantUsageQuotaSnapshot; + }; + copilotUsage?: AssistantUsageCopilotUsage; + /** + * Reasoning effort level used for model calls, if applicable (e.g. "low", "medium", "high", "xhigh") + */ + reasoningEffort?: string; +} +export interface AssistantUsageQuotaSnapshot { + /** + * Whether the user has an unlimited usage entitlement + */ + isUnlimitedEntitlement: boolean; + /** + * Total requests allowed by the entitlement + */ + entitlementRequests: number; + /** + * Number of requests already consumed + */ + usedRequests: number; + /** + * Whether usage is still permitted after quota exhaustion + */ + usageAllowedWithExhaustedQuota: boolean; + /** + * Number of requests over the entitlement limit + */ + overage: number; + /** + * Whether overage is allowed when quota is exhausted + */ + overageAllowedWithExhaustedQuota: boolean; + /** + * Percentage of quota remaining (0.0 to 1.0) + */ + remainingPercentage: number; + /** + * Date when the quota resets + */ + resetDate?: string; +} +/** + * Per-request cost and usage data from the CAPI copilot_usage response field + */ +export interface AssistantUsageCopilotUsage { + /** + * Itemized token usage breakdown + */ + tokenDetails: AssistantUsageCopilotUsageTokenDetail[]; + /** + * Total cost in nano-AIU (AI Units) for this request + */ + totalNanoAiu: number; +} +/** + * Token usage detail for a single billing category + */ +export interface AssistantUsageCopilotUsageTokenDetail { + /** + * Number of tokens in this billing batch + */ + batchSize: number; + /** + * Cost per batch of tokens + */ + costPerBatch: number; + /** + * Total token count for this entry + */ + tokenCount: number; + /** + * Token category (e.g., "input", "output") + */ + tokenType: string; +} +export interface AbortEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "abort"; + data: AbortData; +} +/** + * Turn abort information including the reason for termination + */ +export interface AbortData { + /** + * Reason the current turn was aborted (e.g., "user initiated") + */ + reason: string; +} +export interface ToolUserRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "tool.user_requested"; + data: ToolUserRequestedData; +} +/** + * User-initiated tool invocation request with tool name and arguments + */ +export interface ToolUserRequestedData { + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Name of the tool the user wants to invoke + */ + toolName: string; + /** + * Arguments for the tool invocation + */ + arguments?: { + [k: string]: unknown; + }; +} +export interface ToolExecutionStartEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "tool.execution_start"; + data: ToolExecutionStartData; +} +/** + * Tool execution startup details including MCP server information when applicable + */ +export interface ToolExecutionStartData { + /** + * Unique identifier for this tool call + */ + toolCallId: string; + /** + * Name of the tool being executed + */ + toolName: string; + /** + * Arguments passed to the tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * Name of the MCP server hosting this tool, when the tool is an MCP tool + */ + mcpServerName?: string; + /** + * Original tool name on the MCP server, when the tool is an MCP tool + */ + mcpToolName?: string; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; +} +export interface ToolExecutionPartialResultEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "tool.execution_partial_result"; + data: ToolExecutionPartialData; +} +/** + * Streaming tool execution output for incremental result display + */ +export interface ToolExecutionPartialData { + /** + * Tool call ID this partial result belongs to + */ + toolCallId: string; + /** + * Incremental output chunk from the running tool + */ + partialOutput: string; +} +export interface ToolExecutionProgressEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "tool.execution_progress"; + data: ToolExecutionProgressData; +} +/** + * Tool execution progress notification with status message + */ +export interface ToolExecutionProgressData { + /** + * Tool call ID this progress notification belongs to + */ + toolCallId: string; + /** + * Human-readable progress status message (e.g., from an MCP server) + */ + progressMessage: string; +} +export interface ToolExecutionCompleteEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "tool.execution_complete"; + data: ToolExecutionCompleteData; +} +/** + * Tool execution completion results including success status, detailed output, and error information + */ +export interface ToolExecutionCompleteData { + /** + * Unique identifier for the completed tool call + */ + toolCallId: string; + /** + * Whether the tool execution completed successfully + */ + success: boolean; + /** + * Model identifier that generated this tool call + */ + model?: string; + /** + * CAPI interaction ID for correlating this tool execution with upstream telemetry + */ + interactionId?: string; + /** + * Whether this tool call was explicitly requested by the user rather than the assistant + */ + isUserRequested?: boolean; + result?: ToolExecutionCompleteResult; + error?: ToolExecutionCompleteError; + /** + * Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) + */ + toolTelemetry?: { + [k: string]: unknown; + }; + /** + * @deprecated + * Tool call ID of the parent tool invocation when this event originates from a sub-agent + */ + parentToolCallId?: string; +} +/** + * Tool execution result on success + */ +export interface ToolExecutionCompleteResult { + /** + * Concise tool result text sent to the LLM for chat completion, potentially truncated for token efficiency + */ + content: string; + /** + * Full detailed tool result for UI/timeline display, preserving complete content such as diffs. Falls back to content when absent. + */ + detailedContent?: string; + /** + * Structured content blocks (text, images, audio, resources) returned by the tool in their native format + */ + contents?: ( + | ToolExecutionCompleteContentText + | ToolExecutionCompleteContentTerminal + | ToolExecutionCompleteContentImage + | ToolExecutionCompleteContentAudio + | ToolExecutionCompleteContentResourceLink + | ToolExecutionCompleteContentResource + )[]; +} +/** + * Plain text content block + */ +export interface ToolExecutionCompleteContentText { + /** + * Content block type discriminator + */ + type: "text"; + /** + * The text content + */ + text: string; +} +/** + * Terminal/shell output content block with optional exit code and working directory + */ +export interface ToolExecutionCompleteContentTerminal { + /** + * Content block type discriminator + */ + type: "terminal"; + /** + * Terminal/shell output text + */ + text: string; + /** + * Process exit code, if the command has completed + */ + exitCode?: number; + /** + * Working directory where the command was executed + */ + cwd?: string; +} +/** + * Image content block with base64-encoded data + */ +export interface ToolExecutionCompleteContentImage { + /** + * Content block type discriminator + */ + type: "image"; + /** + * Base64-encoded image data + */ + data: string; + /** + * MIME type of the image (e.g., image/png, image/jpeg) + */ + mimeType: string; +} +/** + * Audio content block with base64-encoded data + */ +export interface ToolExecutionCompleteContentAudio { + /** + * Content block type discriminator + */ + type: "audio"; + /** + * Base64-encoded audio data + */ + data: string; + /** + * MIME type of the audio (e.g., audio/wav, audio/mpeg) + */ + mimeType: string; +} +/** + * Resource link content block referencing an external resource + */ +export interface ToolExecutionCompleteContentResourceLink { + /** + * Icons associated with this resource + */ + icons?: ToolExecutionCompleteContentResourceLinkIcon[]; + /** + * Resource name identifier + */ + name: string; + /** + * URI identifying the resource + */ + uri: string; + /** + * Human-readable description of the resource + */ + description?: string; + /** + * MIME type of the resource content + */ + mimeType?: string; + /** + * Size of the resource in bytes + */ + size?: number; + /** + * Content block type discriminator + */ + type: "resource_link"; +} +/** + * Icon image for a resource + */ +export interface ToolExecutionCompleteContentResourceLinkIcon { + /** + * URL or path to the icon image + */ + src: string; + /** + * MIME type of the icon image + */ + mimeType?: string; + /** + * Available icon sizes (e.g., ['16x16', '32x32']) + */ + sizes?: string[]; + /** + * Theme variant this icon is intended for + */ + theme?: "light" | "dark"; +} +/** + * Embedded resource content block with inline text or binary data + */ +export interface ToolExecutionCompleteContentResource { + /** + * Content block type discriminator + */ + type: "resource"; + /** + * The embedded resource contents, either text or base64-encoded binary + */ + resource: EmbeddedTextResourceContents | EmbeddedBlobResourceContents; +} +export interface EmbeddedTextResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the text content + */ + mimeType?: string; + /** + * Text content of the resource + */ + text: string; +} +export interface EmbeddedBlobResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the blob content + */ + mimeType?: string; + /** + * Base64-encoded binary content of the resource + */ + blob: string; +} +/** + * Error details when the tool execution failed + */ +export interface ToolExecutionCompleteError { + /** + * Human-readable error message + */ + message: string; + /** + * Machine-readable error code + */ + code?: string; +} +export interface SkillInvokedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "skill.invoked"; + data: SkillInvokedData; +} +/** + * Skill invocation details including content, allowed tools, and plugin metadata + */ +export interface SkillInvokedData { + /** + * Name of the invoked skill + */ + name: string; + /** + * File path to the SKILL.md definition + */ + path: string; + /** + * Full content of the skill file, injected into the conversation for the model + */ + content: string; + /** + * Tool names that should be auto-approved when this skill is active + */ + allowedTools?: string[]; + /** + * Name of the plugin this skill originated from, when applicable + */ + pluginName?: string; + /** + * Version of the plugin this skill originated from, when applicable + */ + pluginVersion?: string; + /** + * Description of the skill from its SKILL.md frontmatter + */ + description?: string; +} +export interface SubagentStartedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "subagent.started"; + data: SubagentStartedData; +} +/** + * Sub-agent startup details including parent tool call and agent information + */ +export interface SubagentStartedData { + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Description of what the sub-agent does + */ + agentDescription: string; +} +export interface SubagentCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "subagent.completed"; + data: SubagentCompletedData; +} +/** + * Sub-agent completion details for successful execution + */ +export interface SubagentCompletedData { + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Model used by the sub-agent + */ + model?: string; + /** + * Total number of tool calls made by the sub-agent + */ + totalToolCalls?: number; + /** + * Total tokens (input + output) consumed by the sub-agent + */ + totalTokens?: number; + /** + * Wall-clock duration of the sub-agent execution in milliseconds + */ + durationMs?: number; +} +export interface SubagentFailedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "subagent.failed"; + data: SubagentFailedData; +} +/** + * Sub-agent failure details including error message and agent information + */ +export interface SubagentFailedData { + /** + * Tool call ID of the parent tool invocation that spawned this sub-agent + */ + toolCallId: string; + /** + * Internal name of the sub-agent + */ + agentName: string; + /** + * Human-readable display name of the sub-agent + */ + agentDisplayName: string; + /** + * Error message describing why the sub-agent failed + */ + error: string; + /** + * Model used by the sub-agent (if any model calls succeeded before failure) + */ + model?: string; + /** + * Total number of tool calls made before the sub-agent failed + */ + totalToolCalls?: number; + /** + * Total tokens (input + output) consumed before the sub-agent failed + */ + totalTokens?: number; + /** + * Wall-clock duration of the sub-agent execution in milliseconds + */ + durationMs?: number; +} +export interface SubagentSelectedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "subagent.selected"; + data: SubagentSelectedData; +} +/** + * Custom agent selection details including name and available tools + */ +export interface SubagentSelectedData { + /** + * Internal name of the selected custom agent + */ + agentName: string; + /** + * Human-readable display name of the selected custom agent + */ + agentDisplayName: string; + /** + * List of tool names available to this agent, or null for all tools + */ + tools: string[] | null; +} +export interface SubagentDeselectedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "subagent.deselected"; + data: SubagentDeselectedData; +} +/** + * Empty payload; the event signals that the custom agent was deselected, returning to the default agent + */ +export interface SubagentDeselectedData {} +export interface HookStartEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "hook.start"; + data: HookStartData; +} +/** + * Hook invocation start details including type and input data + */ +export interface HookStartData { + /** + * Unique identifier for this hook invocation + */ + hookInvocationId: string; + /** + * Type of hook being invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + */ + hookType: string; + /** + * Input data passed to the hook + */ + input?: { + [k: string]: unknown; + }; +} +export interface HookEndEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "hook.end"; + data: HookEndData; +} +/** + * Hook invocation completion details including output, success status, and error information + */ +export interface HookEndData { + /** + * Identifier matching the corresponding hook.start event + */ + hookInvocationId: string; + /** + * Type of hook that was invoked (e.g., "preToolUse", "postToolUse", "sessionStart") + */ + hookType: string; + /** + * Output data produced by the hook + */ + output?: { + [k: string]: unknown; + }; + /** + * Whether the hook completed successfully + */ + success: boolean; + error?: HookEndError; +} +/** + * Error details when the hook failed + */ +export interface HookEndError { + /** + * Human-readable error message + */ + message: string; + /** + * Error stack trace, when available + */ + stack?: string; +} +export interface SystemMessageEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "system.message"; + data: SystemMessageData; +} +/** + * System/developer instruction content with role and optional template metadata + */ +export interface SystemMessageData { + /** + * The system or developer prompt text sent as model input + */ + content: string; + /** + * Message role: "system" for system prompts, "developer" for developer-injected instructions + */ + role: "system" | "developer"; + /** + * Optional name identifier for the message source + */ + name?: string; + metadata?: SystemMessageMetadata; +} +/** + * Metadata about the prompt template and its construction + */ +export interface SystemMessageMetadata { + /** + * Version identifier of the prompt template used + */ + promptVersion?: string; + /** + * Template variables used when constructing the prompt + */ + variables?: { + [k: string]: unknown; + }; +} +export interface SystemNotificationEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "system.notification"; + data: SystemNotificationData; +} +/** + * System-generated notification for runtime events like background task completion + */ +export interface SystemNotificationData { + /** + * The notification text, typically wrapped in XML tags + */ + content: string; + /** + * Structured metadata identifying what triggered this notification + */ + kind: + | SystemNotificationAgentCompleted + | SystemNotificationAgentIdle + | SystemNotificationShellCompleted + | SystemNotificationShellDetachedCompleted; +} +export interface SystemNotificationAgentCompleted { + type: "agent_completed"; + /** + * Unique identifier of the background agent + */ + agentId: string; + /** + * Type of the agent (e.g., explore, task, general-purpose) + */ + agentType: string; + /** + * Whether the agent completed successfully or failed + */ + status: "completed" | "failed"; + /** + * Human-readable description of the agent task + */ + description?: string; + /** + * The full prompt given to the background agent + */ + prompt?: string; +} +export interface SystemNotificationAgentIdle { + type: "agent_idle"; + /** + * Unique identifier of the background agent + */ + agentId: string; + /** + * Type of the agent (e.g., explore, task, general-purpose) + */ + agentType: string; + /** + * Human-readable description of the agent task + */ + description?: string; +} +export interface SystemNotificationShellCompleted { + type: "shell_completed"; + /** + * Unique identifier of the shell session + */ + shellId: string; + /** + * Exit code of the shell command, if available + */ + exitCode?: number; + /** + * Human-readable description of the command + */ + description?: string; +} +export interface SystemNotificationShellDetachedCompleted { + type: "shell_detached_completed"; + /** + * Unique identifier of the detached shell session + */ + shellId: string; + /** + * Human-readable description of the command + */ + description?: string; +} +export interface PermissionRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "permission.requested"; + data: PermissionRequestedData; +} +/** + * Permission request notification requiring client approval with request details + */ +export interface PermissionRequestedData { + /** + * Unique identifier for this permission request; used to respond via session.respondToPermission() + */ + requestId: string; + /** + * Details of the permission being requested + */ + permissionRequest: + | PermissionRequestShell + | PermissionRequestWrite + | PermissionRequestRead + | PermissionRequestMcp + | PermissionRequestUrl + | PermissionRequestMemory + | PermissionRequestCustomTool + | PermissionRequestHook; + /** + * When true, this permission was already resolved by a permissionRequest hook and requires no client action + */ + resolvedByHook?: boolean; +} +/** + * Shell command permission request + */ +export interface PermissionRequestShell { + /** + * Permission kind discriminator + */ + kind: "shell"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * The complete shell command text to be executed + */ + fullCommandText: string; + /** + * Human-readable description of what the command intends to do + */ + intention: string; + /** + * Parsed command identifiers found in the command text + */ + commands: PermissionRequestShellCommand[]; + /** + * File paths that may be read or written by the command + */ + possiblePaths: string[]; + /** + * URLs that may be accessed by the command + */ + possibleUrls: PermissionRequestShellPossibleUrl[]; + /** + * Whether the command includes a file write redirection (e.g., > or >>) + */ + hasWriteFileRedirection: boolean; + /** + * Whether the UI can offer session-wide approval for this command pattern + */ + canOfferSessionApproval: boolean; + /** + * Optional warning message about risks of running this command + */ + warning?: string; +} +export interface PermissionRequestShellCommand { + /** + * Command identifier (e.g., executable name) + */ + identifier: string; + /** + * Whether this command is read-only (no side effects) + */ + readOnly: boolean; +} +export interface PermissionRequestShellPossibleUrl { + /** + * URL that may be accessed by the command + */ + url: string; +} +/** + * File write permission request + */ +export interface PermissionRequestWrite { + /** + * Permission kind discriminator + */ + kind: "write"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Human-readable description of the intended file change + */ + intention: string; + /** + * Path of the file being written to + */ + fileName: string; + /** + * Unified diff showing the proposed changes + */ + diff: string; + /** + * Complete new file contents for newly created files + */ + newFileContents?: string; + /** + * Whether the UI can offer session-wide approval for file write operations + */ + canOfferSessionApproval: boolean; +} +/** + * File or directory read permission request + */ +export interface PermissionRequestRead { + /** + * Permission kind discriminator + */ + kind: "read"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Human-readable description of why the file is being read + */ + intention: string; + /** + * Path of the file or directory being read + */ + path: string; +} +/** + * MCP tool invocation permission request + */ +export interface PermissionRequestMcp { + /** + * Permission kind discriminator + */ + kind: "mcp"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Name of the MCP server providing the tool + */ + serverName: string; + /** + * Internal name of the MCP tool + */ + toolName: string; + /** + * Human-readable title of the MCP tool + */ + toolTitle: string; + /** + * Arguments to pass to the MCP tool + */ + args?: { + [k: string]: unknown; + }; + /** + * Whether this MCP tool is read-only (no side effects) + */ + readOnly: boolean; +} +/** + * URL access permission request + */ +export interface PermissionRequestUrl { + /** + * Permission kind discriminator + */ + kind: "url"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Human-readable description of why the URL is being accessed + */ + intention: string; + /** + * URL to be fetched + */ + url: string; +} +/** + * Memory operation permission request + */ +export interface PermissionRequestMemory { + /** + * Permission kind discriminator + */ + kind: "memory"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Whether this is a store or vote memory operation + */ + action?: "store" | "vote"; + /** + * Topic or subject of the memory (store only) + */ + subject?: string; + /** + * The fact being stored or voted on + */ + fact: string; + /** + * Source references for the stored fact (store only) + */ + citations?: string; + /** + * Vote direction (vote only) + */ + direction?: "upvote" | "downvote"; + /** + * Reason for the vote (vote only) + */ + reason?: string; +} +/** + * Custom tool invocation permission request + */ +export interface PermissionRequestCustomTool { + /** + * Permission kind discriminator + */ + kind: "custom-tool"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Name of the custom tool + */ + toolName: string; + /** + * Description of what the custom tool does + */ + toolDescription: string; + /** + * Arguments to pass to the custom tool + */ + args?: { + [k: string]: unknown; + }; +} +/** + * Hook confirmation permission request + */ +export interface PermissionRequestHook { + /** + * Permission kind discriminator + */ + kind: "hook"; + /** + * Tool call ID that triggered this permission request + */ + toolCallId?: string; + /** + * Name of the tool the hook is gating + */ + toolName: string; + /** + * Arguments of the tool call being gated + */ + toolArgs?: { + [k: string]: unknown; + }; + /** + * Optional message from the hook explaining why confirmation is needed + */ + hookMessage?: string; +} +export interface PermissionCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "permission.completed"; + data: PermissionCompletedData; +} +/** + * Permission request completion notification signaling UI dismissal + */ +export interface PermissionCompletedData { + /** + * Request ID of the resolved permission request; clients should dismiss any UI for this request + */ + requestId: string; + result: PermissionCompletedResult; +} +/** + * The result of the permission request + */ +export interface PermissionCompletedResult { + /** + * The outcome of the permission request + */ + kind: + | "approved" + | "denied-by-rules" + | "denied-no-approval-rule-and-could-not-request-from-user" + | "denied-interactively-by-user" + | "denied-by-content-exclusion-policy" + | "denied-by-permission-request-hook"; +} +export interface UserInputRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "user_input.requested"; + data: UserInputRequestedData; +} +/** + * User input request notification with question and optional predefined choices + */ +export interface UserInputRequestedData { + /** + * Unique identifier for this input request; used to respond via session.respondToUserInput() + */ + requestId: string; + /** + * The question or prompt to present to the user + */ + question: string; + /** + * Predefined choices for the user to select from, if applicable + */ + choices?: string[]; + /** + * Whether the user can provide a free-form text response in addition to predefined choices + */ + allowFreeform?: boolean; + /** + * The LLM-assigned tool call ID that triggered this request; used by remote UIs to correlate responses + */ + toolCallId?: string; +} +export interface UserInputCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "user_input.completed"; + data: UserInputCompletedData; +} +/** + * User input request completion with the user's response + */ +export interface UserInputCompletedData { + /** + * Request ID of the resolved user input request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * The user's answer to the input request + */ + answer?: string; + /** + * Whether the answer was typed as free-form text rather than selected from choices + */ + wasFreeform?: boolean; +} +export interface ElicitationRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "elicitation.requested"; + data: ElicitationRequestedData; +} +/** + * Elicitation request; may be form-based (structured input) or URL-based (browser redirect) + */ +export interface ElicitationRequestedData { + /** + * Unique identifier for this elicitation request; used to respond via session.respondToElicitation() + */ + requestId: string; + /** + * Tool call ID from the LLM completion; used to correlate with CompletionChunk.toolCall.id for remote UIs + */ + toolCallId?: string; + /** + * The source that initiated the request (MCP server name, or absent for agent-initiated) + */ + elicitationSource?: string; + /** + * Message describing what information is needed from the user + */ + message: string; + /** + * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. + */ + mode?: "form" | "url"; + requestedSchema?: ElicitationRequestedSchema; + /** + * URL to open in the user's browser (url mode only) + */ + url?: string; + [k: string]: unknown; +} +/** + * JSON Schema describing the form fields to present to the user (form mode only) + */ +export interface ElicitationRequestedSchema { + /** + * Schema type indicator (always 'object') + */ + type: "object"; + /** + * Form field definitions, keyed by field name + */ + properties: { + [k: string]: unknown; + }; + /** + * List of required field names + */ + required?: string[]; +} +export interface ElicitationCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "elicitation.completed"; + data: ElicitationCompletedData; +} +/** + * Elicitation request completion with the user's response + */ +export interface ElicitationCompletedData { + /** + * Request ID of the resolved elicitation request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) + */ + action?: "accept" | "decline" | "cancel"; + /** + * The submitted form data when action is 'accept'; keys match the requested schema fields + */ + content?: { + [k: string]: string | number | boolean | string[]; + }; +} +export interface SamplingRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "sampling.requested"; + data: SamplingRequestedData; +} +/** + * Sampling request from an MCP server; contains the server name and a requestId for correlation + */ +export interface SamplingRequestedData { + /** + * Unique identifier for this sampling request; used to respond via session.respondToSampling() + */ + requestId: string; + /** + * Name of the MCP server that initiated the sampling request + */ + serverName: string; + /** + * The JSON-RPC request ID from the MCP protocol + */ + mcpRequestId: string | number; + [k: string]: unknown; +} +export interface SamplingCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "sampling.completed"; + data: SamplingCompletedData; +} +/** + * Sampling request completion notification signaling UI dismissal + */ +export interface SamplingCompletedData { + /** + * Request ID of the resolved sampling request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface McpOauthRequiredEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "mcp.oauth_required"; + data: McpOauthRequiredData; +} +/** + * OAuth authentication request for an MCP server + */ +export interface McpOauthRequiredData { + /** + * Unique identifier for this OAuth request; used to respond via session.respondToMcpOAuth() + */ + requestId: string; + /** + * Display name of the MCP server that requires OAuth + */ + serverName: string; + /** + * URL of the MCP server that requires OAuth + */ + serverUrl: string; + staticClientConfig?: McpOauthRequiredStaticClientConfig; +} +/** + * Static OAuth client configuration, if the server specifies one + */ +export interface McpOauthRequiredStaticClientConfig { + /** + * OAuth client ID for the server + */ + clientId: string; + /** + * Whether this is a public OAuth client + */ + publicClient?: boolean; +} +export interface McpOauthCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "mcp.oauth_completed"; + data: McpOauthCompletedData; +} +/** + * MCP OAuth request completion notification + */ +export interface McpOauthCompletedData { + /** + * Request ID of the resolved OAuth request + */ + requestId: string; +} +export interface ExternalToolRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "external_tool.requested"; + data: ExternalToolRequestedData; +} +/** + * External tool invocation request for client-side tool execution + */ +export interface ExternalToolRequestedData { + /** + * Unique identifier for this request; used to respond via session.respondToExternalTool() + */ + requestId: string; + /** + * Session ID that this external tool request belongs to + */ + sessionId: string; + /** + * Tool call ID assigned to this external tool invocation + */ + toolCallId: string; + /** + * Name of the external tool to invoke + */ + toolName: string; + /** + * Arguments to pass to the external tool + */ + arguments?: { + [k: string]: unknown; + }; + /** + * W3C Trace Context traceparent header for the execute_tool span + */ + traceparent?: string; + /** + * W3C Trace Context tracestate header for the execute_tool span + */ + tracestate?: string; +} +export interface ExternalToolCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "external_tool.completed"; + data: ExternalToolCompletedData; +} +/** + * External tool completion notification signaling UI dismissal + */ +export interface ExternalToolCompletedData { + /** + * Request ID of the resolved external tool request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface CommandQueuedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "command.queued"; + data: CommandQueuedData; +} +/** + * Queued slash command dispatch request for client execution + */ +export interface CommandQueuedData { + /** + * Unique identifier for this request; used to respond via session.respondToQueuedCommand() + */ + requestId: string; + /** + * The slash command text to be executed (e.g., /help, /clear) + */ + command: string; +} +export interface CommandExecuteEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "command.execute"; + data: CommandExecuteData; +} +/** + * Registered command dispatch request routed to the owning client + */ +export interface CommandExecuteData { + /** + * Unique identifier; used to respond via session.commands.handlePendingCommand() + */ + requestId: string; + /** + * The full command text (e.g., /deploy production) + */ + command: string; + /** + * Command name without leading / + */ + commandName: string; + /** + * Raw argument string after the command name + */ + args: string; +} +export interface CommandCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "command.completed"; + data: CommandCompletedData; +} +/** + * Queued command completion notification signaling UI dismissal + */ +export interface CommandCompletedData { + /** + * Request ID of the resolved command request; clients should dismiss any UI for this request + */ + requestId: string; +} +export interface CommandsChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "commands.changed"; + data: CommandsChangedData; +} +/** + * SDK command registration change notification + */ +export interface CommandsChangedData { + /** + * Current list of registered SDK commands + */ + commands: CommandsChangedCommand[]; +} +export interface CommandsChangedCommand { + name: string; + description?: string; +} +export interface CapabilitiesChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "capabilities.changed"; + data: CapabilitiesChangedData; +} +/** + * Session capability change notification + */ +export interface CapabilitiesChangedData { + ui?: CapabilitiesChangedUI; +} +/** + * UI capability changes + */ +export interface CapabilitiesChangedUI { + /** + * Whether elicitation is now supported + */ + elicitation?: boolean; +} +export interface ExitPlanModeRequestedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "exit_plan_mode.requested"; + data: ExitPlanModeRequestedData; +} +/** + * Plan approval request with plan content and available user actions + */ +export interface ExitPlanModeRequestedData { + /** + * Unique identifier for this request; used to respond via session.respondToExitPlanMode() + */ + requestId: string; + /** + * Summary of the plan that was created + */ + summary: string; + /** + * Full content of the plan file + */ + planContent: string; + /** + * Available actions the user can take (e.g., approve, edit, reject) + */ + actions: string[]; + /** + * The recommended action for the user to take + */ + recommendedAction: string; +} +export interface ExitPlanModeCompletedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "exit_plan_mode.completed"; + data: ExitPlanModeCompletedData; +} +/** + * Plan mode exit completion with the user's approval decision and optional feedback + */ +export interface ExitPlanModeCompletedData { + /** + * Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request + */ + requestId: string; + /** + * Whether the plan was approved by the user + */ + approved?: boolean; + /** + * Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') + */ + selectedAction?: string; + /** + * Whether edits should be auto-approved without confirmation + */ + autoApproveEdits?: boolean; + /** + * Free-form feedback from the user if they requested changes to the plan + */ + feedback?: string; +} +export interface ToolsUpdatedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.tools_updated"; + data: ToolsUpdatedData; +} +export interface ToolsUpdatedData { + model: string; +} +export interface BackgroundTasksChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.background_tasks_changed"; + data: BackgroundTasksChangedData; +} +export interface BackgroundTasksChangedData {} +export interface SkillsLoadedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.skills_loaded"; + data: SkillsLoadedData; +} +export interface SkillsLoadedData { + /** + * Array of resolved skill metadata + */ + skills: SkillsLoadedSkill[]; +} +export interface SkillsLoadedSkill { + /** + * Unique identifier for the skill + */ + name: string; + /** + * Description of what the skill does + */ + description: string; + /** + * Source location type of the skill (e.g., project, personal, plugin) + */ + source: string; + /** + * Whether the skill can be invoked by the user as a slash command + */ + userInvocable: boolean; + /** + * Whether the skill is currently enabled + */ + enabled: boolean; + /** + * Absolute path to the skill file, if available + */ + path?: string; +} +export interface CustomAgentsUpdatedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.custom_agents_updated"; + data: CustomAgentsUpdatedData; +} +export interface CustomAgentsUpdatedData { + /** + * Array of loaded custom agent metadata + */ + agents: CustomAgentsUpdatedAgent[]; + /** + * Non-fatal warnings from agent loading + */ + warnings: string[]; + /** + * Fatal errors from agent loading + */ + errors: string[]; +} +export interface CustomAgentsUpdatedAgent { + /** + * Unique identifier for the agent + */ + id: string; + /** + * Internal name of the agent + */ + name: string; + /** + * Human-readable display name + */ + displayName: string; + /** + * Description of what the agent does + */ + description: string; + /** + * Source location: user, project, inherited, remote, or plugin + */ + source: string; + /** + * List of tool names available to this agent + */ + tools: string[]; + /** + * Whether the agent can be selected by the user + */ + userInvocable: boolean; + /** + * Model override for this agent, if set + */ + model?: string; +} +export interface McpServersLoadedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.mcp_servers_loaded"; + data: McpServersLoadedData; +} +export interface McpServersLoadedData { + /** + * Array of MCP server status summaries + */ + servers: McpServersLoadedServer[]; +} +export interface McpServersLoadedServer { + /** + * Server name (config key) + */ + name: string; + /** + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ + status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; + /** + * Configuration source: user, workspace, plugin, or builtin + */ + source?: string; + /** + * Error message if the server failed to connect + */ + error?: string; +} +export interface McpServerStatusChangedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.mcp_server_status_changed"; + data: McpServerStatusChangedData; +} +export interface McpServerStatusChangedData { + /** + * Name of the MCP server whose status changed + */ + serverName: string; + /** + * New connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ + status: "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; +} +export interface ExtensionsLoadedEvent { + /** + * Unique event identifier (UUID v4), generated when the event is emitted + */ + id: string; + /** + * ISO 8601 timestamp when the event was created + */ + timestamp: string; + /** + * ID of the chronologically preceding event in the session, forming a linked chain. Null for the first event. + */ + parentId: string | null; + ephemeral: true; + /** + * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. + */ + agentId?: string; + type: "session.extensions_loaded"; + data: ExtensionsLoadedData; +} +export interface ExtensionsLoadedData { + /** + * Array of discovered extensions and their status + */ + extensions: ExtensionsLoadedExtension[]; +} +export interface ExtensionsLoadedExtension { + /** + * Source-qualified extension ID (e.g., 'project:my-ext', 'user:auth-helper') + */ + id: string; + /** + * Extension name (directory name) + */ + name: string; + /** + * Discovery source + */ + source: "project" | "user"; + /** + * Current status: running, disabled, failed, or starting + */ + status: "running" | "disabled" | "failed" | "starting"; } diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 9b2df4193..351f95136 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -739,9 +739,8 @@ export type SystemMessageConfig = * Permission request types from the server */ export interface PermissionRequest { - kind: "shell" | "write" | "mcp" | "read" | "url" | "custom-tool"; + kind: "shell" | "write" | "mcp" | "read" | "url" | "custom-tool" | "memory" | "hook"; toolCallId?: string; - [key: string]: unknown; } import type { PermissionDecisionRequest } from "./generated/rpc.js"; diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index cf9d82ea0..f84d5563b 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -25,14 +25,17 @@ def from_int(x: Any) -> int: assert isinstance(x, int) and not isinstance(x, bool) return x -def from_list(f: Callable[[Any], T], x: Any) -> list[T]: - assert isinstance(x, list) - return [f(y) for y in x] - -def from_str(x: Any) -> str: - assert isinstance(x, str) +def from_bool(x: Any) -> bool: + assert isinstance(x, bool) return x +def from_float(x: Any) -> float: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return float(x) + +def from_datetime(x: Any) -> datetime: + return dateutil.parser.parse(x) + def from_none(x: Any) -> Any: assert x is None return x @@ -45,597 +48,453 @@ def from_union(fs, x): pass assert False +def to_float(x: Any) -> float: + assert isinstance(x, (int, float)) + return x + +def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: + assert isinstance(x, dict) + return { k: f(v) for (k, v) in x.items() } + def to_class(c: type[T], x: Any) -> dict: assert isinstance(x, c) return cast(Any, x).to_dict() -def from_bool(x: Any) -> bool: - assert isinstance(x, bool) +def from_str(x: Any) -> str: + assert isinstance(x, str) return x -def from_dict(f: Callable[[Any], T], x: Any) -> dict[str, T]: - assert isinstance(x, dict) - return { k: f(v) for (k, v) in x.items() } +def from_list(f: Callable[[Any], T], x: Any) -> list[T]: + assert isinstance(x, list) + return [f(y) for y in x] def to_enum(c: type[EnumT], x: Any) -> EnumT: assert isinstance(x, c) return x.value -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) +@dataclass +class AccountQuotaSnapshot: + entitlement_requests: int + """Number of requests included in the entitlement""" -def to_float(x: Any) -> float: - assert isinstance(x, (int, float)) - return x + overage: int + """Number of overage requests made this period""" -def from_datetime(x: Any) -> datetime: - return dateutil.parser.parse(x) + overage_allowed_with_exhausted_quota: bool + """Whether pay-per-request usage is allowed when quota is exhausted""" -@dataclass -class ModelCapabilitiesSupports: - """Feature flags indicating what the model supports""" + remaining_percentage: float + """Percentage of entitlement remaining""" - reasoning_effort: bool | None = None - """Whether this model supports reasoning effort configuration""" + used_requests: int + """Number of requests used so far this period""" - vision: bool | None = None - """Whether this model supports vision/image input""" + reset_date: datetime | None = None + """Date when the quota resets (ISO 8601)""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesSupports': + def from_dict(obj: Any) -> 'AccountQuotaSnapshot': assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return ModelCapabilitiesSupports(reasoning_effort, vision) + entitlement_requests = from_int(obj.get("entitlementRequests")) + overage = from_int(obj.get("overage")) + overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) + remaining_percentage = from_float(obj.get("remainingPercentage")) + used_requests = from_int(obj.get("usedRequests")) + reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) + return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) def to_dict(self) -> dict: result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) + result["entitlementRequests"] = from_int(self.entitlement_requests) + result["overage"] = from_int(self.overage) + result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) + result["remainingPercentage"] = to_float(self.remaining_percentage) + result["usedRequests"] = from_int(self.used_requests) + if self.reset_date is not None: + result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) return result @dataclass -class ModelCapabilitiesLimitsVision: - """Vision-specific limits""" +class AgentInfo: + """The newly selected custom agent""" - max_prompt_image_size: int - """Maximum image size in bytes""" + description: str + """Description of the agent's purpose""" - max_prompt_images: int - """Maximum number of images per prompt""" + display_name: str + """Human-readable display name""" - supported_media_types: list[str] - """MIME types the model accepts""" + name: str + """Unique identifier of the custom agent""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': + def from_dict(obj: Any) -> 'AgentInfo': assert isinstance(obj, dict) - max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) - max_prompt_images = from_int(obj.get("max_prompt_images")) - supported_media_types = from_list(from_str, obj.get("supported_media_types")) - return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + description = from_str(obj.get("description")) + display_name = from_str(obj.get("displayName")) + name = from_str(obj.get("name")) + return AgentInfo(description, display_name, name) def to_dict(self) -> dict: result: dict = {} - result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) - result["max_prompt_images"] = from_int(self.max_prompt_images) - result["supported_media_types"] = from_list(from_str, self.supported_media_types) + result["description"] = from_str(self.description) + result["displayName"] = from_str(self.display_name) + result["name"] = from_str(self.name) return result -class FilterMappingString(Enum): - HIDDEN_CHARACTERS = "hidden_characters" - MARKDOWN = "markdown" - NONE = "none" - -class MCPServerConfigType(Enum): - """Remote transport type. Defaults to "http" when omitted.""" - - HTTP = "http" - LOCAL = "local" - SSE = "sse" - STDIO = "stdio" - -class MCPServerSource(Enum): - """Configuration source - - Configuration source: user, workspace, plugin, or builtin - """ - BUILTIN = "builtin" - PLUGIN = "plugin" - USER = "user" - WORKSPACE = "workspace" - -class DiscoveredMCPServerType(Enum): - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" - - HTTP = "http" - MEMORY = "memory" - SSE = "sse" - STDIO = "stdio" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ServerSkill: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled (based on global config)""" - +class AgentSelectRequest: name: str - """Unique identifier for the skill""" - - source: str - """Source location type (e.g., project, personal-copilot, plugin, builtin)""" - - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" - - path: str | None = None - """Absolute path to the skill file""" - - project_path: str | None = None - """The project path this skill belongs to (only for project/inherited skills)""" + """Name of the custom agent to select""" @staticmethod - def from_dict(obj: Any) -> 'ServerSkill': + def from_dict(obj: Any) -> 'AgentSelectRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - project_path = from_union([from_str, from_none], obj.get("projectPath")) - return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) + return AgentSelectRequest(name) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) - if self.project_path is not None: - result["projectPath"] = from_union([from_str, from_none], self.project_path) return result @dataclass -class CurrentModel: - model_id: str | None = None - """Currently active model identifier""" +class CommandsHandlePendingCommandRequest: + request_id: str + """Request ID from the command invocation event""" + + error: str | None = None + """Error message if the command handler failed""" @staticmethod - def from_dict(obj: Any) -> 'CurrentModel': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': assert isinstance(obj, dict) - model_id = from_union([from_str, from_none], obj.get("modelId")) - return CurrentModel(model_id) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + return CommandsHandlePendingCommandRequest(request_id, error) def to_dict(self) -> dict: result: dict = {} - if self.model_id is not None: - result["modelId"] = from_union([from_str, from_none], self.model_id) + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) return result @dataclass -class PurpleModelCapabilitiesOverrideLimitsVision: - max_prompt_image_size: int | None = None - """Maximum image size in bytes""" - - max_prompt_images: int | None = None - """Maximum number of images per prompt""" - - supported_media_types: list[str] | None = None - """MIME types the model accepts""" +class CommandsHandlePendingCommandResult: + success: bool + """Whether the command was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'PurpleModelCapabilitiesOverrideLimitsVision': + def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': assert isinstance(obj, dict) - max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) - max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) - supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) - return PurpleModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + success = from_bool(obj.get("success")) + return CommandsHandlePendingCommandResult(success) def to_dict(self) -> dict: result: dict = {} - if self.max_prompt_image_size is not None: - result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) - if self.max_prompt_images is not None: - result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) - if self.supported_media_types is not None: - result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) + result["success"] = from_bool(self.success) return result @dataclass -class ModelCapabilitiesOverrideSupports: - """Feature flags indicating what the model supports""" - - reasoning_effort: bool | None = None - vision: bool | None = None +class CurrentModel: + model_id: str | None = None + """Currently active model identifier""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': + def from_dict(obj: Any) -> 'CurrentModel': assert isinstance(obj, dict) - reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) - vision = from_union([from_bool, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) + model_id = from_union([from_str, from_none], obj.get("modelId")) + return CurrentModel(model_id) def to_dict(self) -> dict: result: dict = {} - if self.reasoning_effort is not None: - result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) - if self.vision is not None: - result["vision"] = from_union([from_bool, from_none], self.vision) + if self.model_id is not None: + result["modelId"] = from_union([from_str, from_none], self.model_id) return result -@dataclass -class AgentInfo: - description: str - """Description of the agent's purpose""" +class MCPServerSource(Enum): + """Configuration source - display_name: str - """Human-readable display name""" + Configuration source: user, workspace, plugin, or builtin + """ + BUILTIN = "builtin" + PLUGIN = "plugin" + USER = "user" + WORKSPACE = "workspace" - name: str - """Unique identifier of the custom agent""" +class DiscoveredMCPServerType(Enum): + """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" - @staticmethod - def from_dict(obj: Any) -> 'AgentInfo': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentInfo(description, display_name, name) + HTTP = "http" + MEMORY = "memory" + SSE = "sse" + STDIO = "stdio" - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) - return result +class ExtensionSource(Enum): + """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" -class MCPServerStatus(Enum): - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" + PROJECT = "project" + USER = "user" + +class ExtensionStatus(Enum): + """Current status: running, disabled, failed, or starting""" - CONNECTED = "connected" DISABLED = "disabled" FAILED = "failed" - NEEDS_AUTH = "needs-auth" - NOT_CONFIGURED = "not_configured" - PENDING = "pending" + RUNNING = "running" + STARTING = "starting" +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ToolCallResult: - text_result_for_llm: str - """Text result to send back to the LLM""" - - error: str | None = None - """Error message if the tool call failed""" - - result_type: str | None = None - """Type of the tool result""" - - tool_telemetry: dict[str, Any] | None = None - """Telemetry data from tool execution""" +class ExtensionsDisableRequest: + id: str + """Source-qualified extension ID to disable""" @staticmethod - def from_dict(obj: Any) -> 'ToolCallResult': + def from_dict(obj: Any) -> 'ExtensionsDisableRequest': assert isinstance(obj, dict) - text_result_for_llm = from_str(obj.get("textResultForLlm")) - error = from_union([from_str, from_none], obj.get("error")) - result_type = from_union([from_str, from_none], obj.get("resultType")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) + id = from_str(obj.get("id")) + return ExtensionsDisableRequest(id) def to_dict(self) -> dict: result: dict = {} - result["textResultForLlm"] = from_str(self.text_result_for_llm) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result_type is not None: - result["resultType"] = from_union([from_str, from_none], self.result_type) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) + result["id"] = from_str(self.id) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HandleToolCallResult: - success: bool - """Whether the tool call result was handled successfully""" +class ExtensionsEnableRequest: + id: str + """Source-qualified extension ID to enable""" @staticmethod - def from_dict(obj: Any) -> 'HandleToolCallResult': + def from_dict(obj: Any) -> 'ExtensionsEnableRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return HandleToolCallResult(success) + id = from_str(obj.get("id")) + return ExtensionsEnableRequest(id) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["id"] = from_str(self.id) return result -class UIElicitationStringEnumFieldType(Enum): - STRING = "string" +class FilterMappingString(Enum): + HIDDEN_CHARACTERS = "hidden_characters" + MARKDOWN = "markdown" + NONE = "none" +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIElicitationStringOneOfFieldOneOf: - const: str - title: str +class FleetStartRequest: + prompt: str | None = None + """Optional user prompt to combine with fleet instructions""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': + def from_dict(obj: Any) -> 'FleetStartRequest': assert isinstance(obj, dict) - const = from_str(obj.get("const")) - title = from_str(obj.get("title")) - return UIElicitationStringOneOfFieldOneOf(const, title) + prompt = from_union([from_str, from_none], obj.get("prompt")) + return FleetStartRequest(prompt) def to_dict(self) -> dict: result: dict = {} - result["const"] = from_str(self.const) - result["title"] = from_str(self.title) + if self.prompt is not None: + result["prompt"] = from_union([from_str, from_none], self.prompt) return result -class UIElicitationArrayEnumFieldType(Enum): - ARRAY = "array" - -class UIElicitationResponseAction(Enum): - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" - - ACCEPT = "accept" - CANCEL = "cancel" - DECLINE = "decline" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UIElicitationResult: - success: bool - """Whether the response was accepted. False if the request was already resolved by another - client. - """ +class FleetStartResult: + started: bool + """Whether fleet mode was successfully activated""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResult': + def from_dict(obj: Any) -> 'FleetStartResult': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return UIElicitationResult(success) + started = from_bool(obj.get("started")) + return FleetStartResult(started) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["started"] = from_bool(self.started) return result -class Kind(Enum): - APPROVED = "approved" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - DENIED_BY_RULES = "denied-by-rules" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - @dataclass -class PermissionRequestResult: +class HandleToolCallResult: success: bool - """Whether the permission request was handled successfully""" + """Whether the tool call result was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'PermissionRequestResult': + def from_dict(obj: Any) -> 'HandleToolCallResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return PermissionRequestResult(success) + return HandleToolCallResult(success) def to_dict(self) -> dict: result: dict = {} result["success"] = from_bool(self.success) return result -class SessionFSErrorCode(Enum): - """Error classification""" +@dataclass +class HistoryCompactContextWindow: + """Post-compaction context window usage breakdown""" - ENOENT = "ENOENT" - UNKNOWN = "UNKNOWN" + current_tokens: int + """Current total tokens in the context window (system + conversation + tool definitions)""" -@dataclass -class PingResult: - message: str - """Echoed message (or default greeting)""" + messages_length: int + """Current number of messages in the conversation""" - protocol_version: int - """Server protocol version number""" + token_limit: int + """Maximum token count for the model's context window""" - timestamp: int - """Server timestamp in milliseconds""" + conversation_tokens: int | None = None + """Token count from non-system messages (user, assistant, tool)""" + + system_tokens: int | None = None + """Token count from system message(s)""" + + tool_definitions_tokens: int | None = None + """Token count from tool definitions""" @staticmethod - def from_dict(obj: Any) -> 'PingResult': + def from_dict(obj: Any) -> 'HistoryCompactContextWindow': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - protocol_version = from_int(obj.get("protocolVersion")) - timestamp = from_int(obj.get("timestamp")) - return PingResult(message, protocol_version, timestamp) + current_tokens = from_int(obj.get("currentTokens")) + messages_length = from_int(obj.get("messagesLength")) + token_limit = from_int(obj.get("tokenLimit")) + conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) + system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) + tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) + return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - result["protocolVersion"] = from_int(self.protocol_version) - result["timestamp"] = from_int(self.timestamp) + result["currentTokens"] = from_int(self.current_tokens) + result["messagesLength"] = from_int(self.messages_length) + result["tokenLimit"] = from_int(self.token_limit) + if self.conversation_tokens is not None: + result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) + if self.system_tokens is not None: + result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) + if self.tool_definitions_tokens is not None: + result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PingRequest: - message: str | None = None - """Optional message to echo back""" +class HistoryTruncateRequest: + event_id: str + """Event ID to truncate to. This event and all events after it are removed from the session.""" @staticmethod - def from_dict(obj: Any) -> 'PingRequest': + def from_dict(obj: Any) -> 'HistoryTruncateRequest': assert isinstance(obj, dict) - message = from_union([from_str, from_none], obj.get("message")) - return PingRequest(message) + event_id = from_str(obj.get("eventId")) + return HistoryTruncateRequest(event_id) def to_dict(self) -> dict: result: dict = {} - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) + result["eventId"] = from_str(self.event_id) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModelBilling: - """Billing information""" - - multiplier: float - """Billing cost multiplier relative to the base rate""" +class HistoryTruncateResult: + events_removed: int + """Number of events that were removed""" @staticmethod - def from_dict(obj: Any) -> 'ModelBilling': + def from_dict(obj: Any) -> 'HistoryTruncateResult': assert isinstance(obj, dict) - multiplier = from_float(obj.get("multiplier")) - return ModelBilling(multiplier) + events_removed = from_int(obj.get("eventsRemoved")) + return HistoryTruncateResult(events_removed) def to_dict(self) -> dict: result: dict = {} - result["multiplier"] = to_float(self.multiplier) + result["eventsRemoved"] = from_int(self.events_removed) return result -@dataclass -class ModelPolicy: - """Policy state (if applicable)""" - - state: str - """Current policy state for this model""" +class InstructionsSourcesLocation(Enum): + """Where this source lives — used for UI grouping""" - terms: str - """Usage terms or conditions for this model""" - - @staticmethod - def from_dict(obj: Any) -> 'ModelPolicy': - assert isinstance(obj, dict) - state = from_str(obj.get("state")) - terms = from_str(obj.get("terms")) - return ModelPolicy(state, terms) - - def to_dict(self) -> dict: - result: dict = {} - result["state"] = from_str(self.state) - result["terms"] = from_str(self.terms) - return result - -@dataclass -class Tool: - description: str - """Description of what the tool does""" + REPOSITORY = "repository" + USER = "user" + WORKING_DIRECTORY = "working-directory" - name: str - """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" +class InstructionsSourcesType(Enum): + """Category of instruction source — used for merge logic""" - instructions: str | None = None - """Optional instructions for how to use this tool effectively""" + CHILD_INSTRUCTIONS = "child-instructions" + HOME = "home" + MODEL = "model" + NESTED_AGENTS = "nested-agents" + REPO = "repo" + VSCODE = "vscode" - namespaced_name: str | None = None - """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP - tools) +class SessionLogLevel(Enum): + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". """ - parameters: dict[str, Any] | None = None - """JSON Schema for the tool's input parameters""" - - @staticmethod - def from_dict(obj: Any) -> 'Tool': - assert isinstance(obj, dict) - description = from_str(obj.get("description")) - name = from_str(obj.get("name")) - instructions = from_union([from_str, from_none], obj.get("instructions")) - namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) - parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) - return Tool(description, name, instructions, namespaced_name, parameters) - - def to_dict(self) -> dict: - result: dict = {} - result["description"] = from_str(self.description) - result["name"] = from_str(self.name) - if self.instructions is not None: - result["instructions"] = from_union([from_str, from_none], self.instructions) - if self.namespaced_name is not None: - result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) - if self.parameters is not None: - result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) - return result + ERROR = "error" + INFO = "info" + WARNING = "warning" @dataclass -class ToolsListRequest: - model: str | None = None - """Optional model ID — when provided, the returned tool list reflects model-specific - overrides - """ +class LogResult: + event_id: UUID + """The unique identifier of the emitted session event""" @staticmethod - def from_dict(obj: Any) -> 'ToolsListRequest': + def from_dict(obj: Any) -> 'LogResult': assert isinstance(obj, dict) - model = from_union([from_str, from_none], obj.get("model")) - return ToolsListRequest(model) + event_id = UUID(obj.get("eventId")) + return LogResult(event_id) def to_dict(self) -> dict: result: dict = {} - if self.model is not None: - result["model"] = from_union([from_str, from_none], self.model) + result["eventId"] = str(self.event_id) return result -@dataclass -class AccountQuotaSnapshot: - entitlement_requests: int - """Number of requests included in the entitlement""" - - overage: int - """Number of overage requests made this period""" - - overage_allowed_with_exhausted_quota: bool - """Whether pay-per-request usage is allowed when quota is exhausted""" - - remaining_percentage: float - """Percentage of entitlement remaining""" +class MCPServerConfigType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" - used_requests: int - """Number of requests used so far this period""" + HTTP = "http" + LOCAL = "local" + SSE = "sse" + STDIO = "stdio" - reset_date: datetime | None = None - """Date when the quota resets (ISO 8601)""" +@dataclass +class MCPConfigRemoveRequest: + name: str + """Name of the MCP server to remove""" @staticmethod - def from_dict(obj: Any) -> 'AccountQuotaSnapshot': + def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': assert isinstance(obj, dict) - entitlement_requests = from_int(obj.get("entitlementRequests")) - overage = from_int(obj.get("overage")) - overage_allowed_with_exhausted_quota = from_bool(obj.get("overageAllowedWithExhaustedQuota")) - remaining_percentage = from_float(obj.get("remainingPercentage")) - used_requests = from_int(obj.get("usedRequests")) - reset_date = from_union([from_datetime, from_none], obj.get("resetDate")) - return AccountQuotaSnapshot(entitlement_requests, overage, overage_allowed_with_exhausted_quota, remaining_percentage, used_requests, reset_date) + name = from_str(obj.get("name")) + return MCPConfigRemoveRequest(name) def to_dict(self) -> dict: result: dict = {} - result["entitlementRequests"] = from_int(self.entitlement_requests) - result["overage"] = from_int(self.overage) - result["overageAllowedWithExhaustedQuota"] = from_bool(self.overage_allowed_with_exhausted_quota) - result["remainingPercentage"] = to_float(self.remaining_percentage) - result["usedRequests"] = from_int(self.used_requests) - if self.reset_date is not None: - result["resetDate"] = from_union([lambda x: x.isoformat(), from_none], self.reset_date) + result["name"] = from_str(self.name) return result @dataclass -class MCPConfigRemoveRequest: - name: str - """Name of the MCP server to remove""" +class MCPDisableRequest: + server_name: str + """Name of the MCP server to disable""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigRemoveRequest': + def from_dict(obj: Any) -> 'MCPDisableRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return MCPConfigRemoveRequest(name) + server_name = from_str(obj.get("serverName")) + return MCPDisableRequest(server_name) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["serverName"] = from_str(self.server_name) return result @dataclass @@ -656,106 +515,191 @@ def to_dict(self) -> dict: return result @dataclass -class SkillsConfigSetDisabledSkillsRequest: - disabled_skills: list[str] - """List of skill names to disable""" +class MCPEnableRequest: + server_name: str + """Name of the MCP server to enable""" @staticmethod - def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': + def from_dict(obj: Any) -> 'MCPEnableRequest': assert isinstance(obj, dict) - disabled_skills = from_list(from_str, obj.get("disabledSkills")) - return SkillsConfigSetDisabledSkillsRequest(disabled_skills) + server_name = from_str(obj.get("serverName")) + return MCPEnableRequest(server_name) def to_dict(self) -> dict: result: dict = {} - result["disabledSkills"] = from_list(from_str, self.disabled_skills) + result["serverName"] = from_str(self.server_name) return result +class MCPServerStatus(Enum): + """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" + + CONNECTED = "connected" + DISABLED = "disabled" + FAILED = "failed" + NEEDS_AUTH = "needs-auth" + NOT_CONFIGURED = "not_configured" + PENDING = "pending" + +class MCPServerConfigHTTPType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" + + HTTP = "http" + SSE = "sse" + +class MCPServerConfigLocalType(Enum): + LOCAL = "local" + STDIO = "stdio" + +class SessionMode(Enum): + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" + + AUTOPILOT = "autopilot" + INTERACTIVE = "interactive" + PLAN = "plan" + @dataclass -class SkillsDiscoverRequest: - project_paths: list[str] | None = None - """Optional list of project directory paths to scan for project-scoped skills""" +class ModelBilling: + """Billing information""" - skill_directories: list[str] | None = None - """Optional list of additional skill directory paths to include""" + multiplier: float + """Billing cost multiplier relative to the base rate""" @staticmethod - def from_dict(obj: Any) -> 'SkillsDiscoverRequest': + def from_dict(obj: Any) -> 'ModelBilling': assert isinstance(obj, dict) - project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) - skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) - return SkillsDiscoverRequest(project_paths, skill_directories) + multiplier = from_float(obj.get("multiplier")) + return ModelBilling(multiplier) def to_dict(self) -> dict: result: dict = {} - if self.project_paths is not None: - result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) - if self.skill_directories is not None: - result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) + result["multiplier"] = to_float(self.multiplier) return result @dataclass -class SessionFSSetProviderResult: - success: bool - """Whether the provider was set successfully""" +class ModelCapabilitiesLimitsVision: + """Vision-specific limits""" + + max_prompt_image_size: int + """Maximum image size in bytes""" + + max_prompt_images: int + """Maximum number of images per prompt""" + + supported_media_types: list[str] + """MIME types the model accepts""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderResult': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsVision': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return SessionFSSetProviderResult(success) + max_prompt_image_size = from_int(obj.get("max_prompt_image_size")) + max_prompt_images = from_int(obj.get("max_prompt_images")) + supported_media_types = from_list(from_str, obj.get("supported_media_types")) + return ModelCapabilitiesLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["max_prompt_image_size"] = from_int(self.max_prompt_image_size) + result["max_prompt_images"] = from_int(self.max_prompt_images) + result["supported_media_types"] = from_list(from_str, self.supported_media_types) return result -class SessionFSSetProviderConventions(Enum): - """Path conventions used by this filesystem""" +@dataclass +class ModelCapabilitiesSupports: + """Feature flags indicating what the model supports""" - POSIX = "posix" - WINDOWS = "windows" + reasoning_effort: bool | None = None + """Whether this model supports reasoning effort configuration""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class SessionsForkResult: - session_id: str - """The new forked session's ID""" + vision: bool | None = None + """Whether this model supports vision/image input""" @staticmethod - def from_dict(obj: Any) -> 'SessionsForkResult': + def from_dict(obj: Any) -> 'ModelCapabilitiesSupports': assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - return SessionsForkResult(session_id) + reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) + vision = from_union([from_bool, from_none], obj.get("vision")) + return ModelCapabilitiesSupports(reasoning_effort, vision) def to_dict(self) -> dict: result: dict = {} - result["sessionId"] = from_str(self.session_id) - return result + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) + if self.vision is not None: + result["vision"] = from_union([from_bool, from_none], self.vision) + return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SessionsForkRequest: - session_id: str - """Source session ID to fork from""" +class ModelPolicy: + """Policy state (if applicable)""" - to_event_id: str | None = None - """Optional event ID boundary. When provided, the fork includes only events before this ID - (exclusive). When omitted, all events are included. - """ + state: str + """Current policy state for this model""" + + terms: str + """Usage terms or conditions for this model""" @staticmethod - def from_dict(obj: Any) -> 'SessionsForkRequest': + def from_dict(obj: Any) -> 'ModelPolicy': assert isinstance(obj, dict) - session_id = from_str(obj.get("sessionId")) - to_event_id = from_union([from_str, from_none], obj.get("toEventId")) - return SessionsForkRequest(session_id, to_event_id) + state = from_str(obj.get("state")) + terms = from_str(obj.get("terms")) + return ModelPolicy(state, terms) def to_dict(self) -> dict: result: dict = {} - result["sessionId"] = from_str(self.session_id) - if self.to_event_id is not None: - result["toEventId"] = from_union([from_str, from_none], self.to_event_id) + result["state"] = from_str(self.state) + result["terms"] = from_str(self.terms) + return result + +@dataclass +class ModelCapabilitiesOverrideLimitsVision: + max_prompt_image_size: int | None = None + """Maximum image size in bytes""" + + max_prompt_images: int | None = None + """Maximum number of images per prompt""" + + supported_media_types: list[str] | None = None + """MIME types the model accepts""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimitsVision': + assert isinstance(obj, dict) + max_prompt_image_size = from_union([from_int, from_none], obj.get("max_prompt_image_size")) + max_prompt_images = from_union([from_int, from_none], obj.get("max_prompt_images")) + supported_media_types = from_union([lambda x: from_list(from_str, x), from_none], obj.get("supported_media_types")) + return ModelCapabilitiesOverrideLimitsVision(max_prompt_image_size, max_prompt_images, supported_media_types) + + def to_dict(self) -> dict: + result: dict = {} + if self.max_prompt_image_size is not None: + result["max_prompt_image_size"] = from_union([from_int, from_none], self.max_prompt_image_size) + if self.max_prompt_images is not None: + result["max_prompt_images"] = from_union([from_int, from_none], self.max_prompt_images) + if self.supported_media_types is not None: + result["supported_media_types"] = from_union([lambda x: from_list(from_str, x), from_none], self.supported_media_types) + return result + +@dataclass +class ModelCapabilitiesOverrideSupports: + """Feature flags indicating what the model supports""" + + reasoning_effort: bool | None = None + vision: bool | None = None + + @staticmethod + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideSupports': + assert isinstance(obj, dict) + reasoning_effort = from_union([from_bool, from_none], obj.get("reasoningEffort")) + vision = from_union([from_bool, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideSupports(reasoning_effort, vision) + + def to_dict(self) -> dict: + result: dict = {} + if self.reasoning_effort is not None: + result["reasoningEffort"] = from_union([from_bool, from_none], self.reasoning_effort) + if self.vision is not None: + result["vision"] = from_union([from_bool, from_none], self.vision) return result @dataclass @@ -775,13 +719,6 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result -class SessionMode(Enum): - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" - - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" - @dataclass class NameGetResult: name: str | None = None @@ -814,6 +751,91 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result +class PermissionDecisionKind(Enum): + APPROVED = "approved" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + DENIED_BY_RULES = "denied-by-rules" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + +class PermissionDecisionApprovedKind(Enum): + APPROVED = "approved" + +class PermissionDecisionDeniedByContentExclusionPolicyKind(Enum): + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + +class PermissionDecisionDeniedByPermissionRequestHookKind(Enum): + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + +class PermissionDecisionDeniedByRulesKind(Enum): + DENIED_BY_RULES = "denied-by-rules" + +class PermissionDecisionDeniedInteractivelyByUserKind(Enum): + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + +class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(Enum): + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + +@dataclass +class PermissionRequestResult: + success: bool + """Whether the permission request was handled successfully""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionRequestResult': + assert isinstance(obj, dict) + success = from_bool(obj.get("success")) + return PermissionRequestResult(success) + + def to_dict(self) -> dict: + result: dict = {} + result["success"] = from_bool(self.success) + return result + +@dataclass +class PingRequest: + message: str | None = None + """Optional message to echo back""" + + @staticmethod + def from_dict(obj: Any) -> 'PingRequest': + assert isinstance(obj, dict) + message = from_union([from_str, from_none], obj.get("message")) + return PingRequest(message) + + def to_dict(self) -> dict: + result: dict = {} + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + return result + +@dataclass +class PingResult: + message: str + """Echoed message (or default greeting)""" + + protocol_version: int + """Server protocol version number""" + + timestamp: int + """Server timestamp in milliseconds""" + + @staticmethod + def from_dict(obj: Any) -> 'PingResult': + assert isinstance(obj, dict) + message = from_str(obj.get("message")) + protocol_version = from_int(obj.get("protocolVersion")) + timestamp = from_int(obj.get("timestamp")) + return PingResult(message, protocol_version, timestamp) + + def to_dict(self) -> dict: + result: dict = {} + result["message"] = from_str(self.message) + result["protocolVersion"] = from_int(self.protocol_version) + result["timestamp"] = from_int(self.timestamp) + return result + @dataclass class PlanReadResult: exists: bool @@ -856,461 +878,437 @@ def to_dict(self) -> dict: result["content"] = from_str(self.content) return result -class HostType(Enum): - ADO = "ado" - GITHUB = "github" +@dataclass +class Plugin: + enabled: bool + """Whether the plugin is currently enabled""" -class SessionSyncLevel(Enum): - LOCAL = "local" - REPO_AND_USER = "repo_and_user" - USER = "user" + marketplace: str + """Marketplace the plugin came from""" -@dataclass -class WorkspacesListFilesResult: - files: list[str] - """Relative file paths in the workspace files directory""" + name: str + """Plugin name""" + + version: str | None = None + """Installed version""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesListFilesResult': + def from_dict(obj: Any) -> 'Plugin': assert isinstance(obj, dict) - files = from_list(from_str, obj.get("files")) - return WorkspacesListFilesResult(files) + enabled = from_bool(obj.get("enabled")) + marketplace = from_str(obj.get("marketplace")) + name = from_str(obj.get("name")) + version = from_union([from_str, from_none], obj.get("version")) + return Plugin(enabled, marketplace, name, version) def to_dict(self) -> dict: result: dict = {} - result["files"] = from_list(from_str, self.files) + result["enabled"] = from_bool(self.enabled) + result["marketplace"] = from_str(self.marketplace) + result["name"] = from_str(self.name) + if self.version is not None: + result["version"] = from_union([from_str, from_none], self.version) return result @dataclass -class WorkspacesReadFileResult: - content: str - """File content as a UTF-8 string""" +class ServerSkill: + description: str + """Description of what the skill does""" + + enabled: bool + """Whether the skill is currently enabled (based on global config)""" + + name: str + """Unique identifier for the skill""" + + source: str + """Source location type (e.g., project, personal-copilot, plugin, builtin)""" + + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" + + path: str | None = None + """Absolute path to the skill file""" + + project_path: str | None = None + """The project path this skill belongs to (only for project/inherited skills)""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesReadFileResult': + def from_dict(obj: Any) -> 'ServerSkill': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - return WorkspacesReadFileResult(content) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + project_path = from_union([from_str, from_none], obj.get("projectPath")) + return ServerSkill(description, enabled, name, source, user_invocable, path, project_path) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) + if self.project_path is not None: + result["projectPath"] = from_union([from_str, from_none], self.project_path) return result @dataclass -class WorkspacesReadFileRequest: +class SessionFSAppendFileRequest: + content: str + """Content to append""" + path: str - """Relative path within the workspace files directory""" + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + mode: int | None = None + """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesReadFileRequest': + def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': assert isinstance(obj, dict) + content = from_str(obj.get("content")) path = from_str(obj.get("path")) - return WorkspacesReadFileRequest(path) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSAppendFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} + result["content"] = from_str(self.content) result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) return result -@dataclass -class WorkspacesCreateFileRequest: - content: str - """File content to write as a UTF-8 string""" +class SessionFSErrorCode(Enum): + """Error classification""" + + ENOENT = "ENOENT" + UNKNOWN = "UNKNOWN" +@dataclass +class SessionFSExistsRequest: path: str - """Relative path within the workspace files directory""" + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': + def from_dict(obj: Any) -> 'SessionFSExistsRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) path = from_str(obj.get("path")) - return WorkspacesCreateFileRequest(content, path) + session_id = from_str(obj.get("sessionId")) + return SessionFSExistsRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -class InstructionsSourcesLocation(Enum): - """Where this source lives — used for UI grouping""" - - REPOSITORY = "repository" - USER = "user" - WORKING_DIRECTORY = "working-directory" - -class InstructionsSourcesType(Enum): - """Category of instruction source — used for merge logic""" - - CHILD_INSTRUCTIONS = "child-instructions" - HOME = "home" - MODEL = "model" - NESTED_AGENTS = "nested-agents" - REPO = "repo" - VSCODE = "vscode" - -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FleetStartResult: - started: bool - """Whether fleet mode was successfully activated""" +class SessionFSExistsResult: + exists: bool + """Whether the path exists""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartResult': + def from_dict(obj: Any) -> 'SessionFSExistsResult': assert isinstance(obj, dict) - started = from_bool(obj.get("started")) - return FleetStartResult(started) + exists = from_bool(obj.get("exists")) + return SessionFSExistsResult(exists) def to_dict(self) -> dict: result: dict = {} - result["started"] = from_bool(self.started) + result["exists"] = from_bool(self.exists) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class FleetStartRequest: - prompt: str | None = None - """Optional user prompt to combine with fleet instructions""" +class SessionFSMkdirRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + mode: int | None = None + """Optional POSIX-style mode for newly created directories""" + + recursive: bool | None = None + """Create parent directories as needed""" @staticmethod - def from_dict(obj: Any) -> 'FleetStartRequest': + def from_dict(obj: Any) -> 'SessionFSMkdirRequest': assert isinstance(obj, dict) - prompt = from_union([from_str, from_none], obj.get("prompt")) - return FleetStartRequest(prompt) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSMkdirRequest(path, session_id, mode, recursive) def to_dict(self) -> dict: result: dict = {} - if self.prompt is not None: - result["prompt"] = from_union([from_str, from_none], self.prompt) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) return result @dataclass -class AgentSelectResultAgent: - """The newly selected custom agent""" - - description: str - """Description of the agent's purpose""" - - display_name: str - """Human-readable display name""" +class SessionFSReadFileRequest: + path: str + """Path using SessionFs conventions""" - name: str - """Unique identifier of the custom agent""" + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectResultAgent': + def from_dict(obj: Any) -> 'SessionFSReadFileRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - display_name = from_str(obj.get("displayName")) - name = from_str(obj.get("name")) - return AgentSelectResultAgent(description, display_name, name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReadFileRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["displayName"] = from_str(self.display_name) - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentSelectRequest: - name: str - """Name of the custom agent to select""" +class SessionFSReaddirRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectRequest': + def from_dict(obj: Any) -> 'SessionFSReaddirRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return AgentSelectRequest(name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -@dataclass -class Skill: - description: str - """Description of what the skill does""" - - enabled: bool - """Whether the skill is currently enabled""" - - name: str - """Unique identifier for the skill""" +class SessionFSReaddirWithTypesEntryType(Enum): + """Entry type""" - source: str - """Source location type (e.g., project, personal, plugin)""" + DIRECTORY = "directory" + FILE = "file" - user_invocable: bool - """Whether the skill can be invoked by the user as a slash command""" +@dataclass +class SessionFSReaddirWithTypesRequest: + path: str + """Path using SessionFs conventions""" - path: str | None = None - """Absolute path to the skill file""" + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'Skill': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': assert isinstance(obj, dict) - description = from_str(obj.get("description")) - enabled = from_bool(obj.get("enabled")) - name = from_str(obj.get("name")) - source = from_str(obj.get("source")) - user_invocable = from_bool(obj.get("userInvocable")) - path = from_union([from_str, from_none], obj.get("path")) - return Skill(description, enabled, name, source, user_invocable, path) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSReaddirWithTypesRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["description"] = from_str(self.description) - result["enabled"] = from_bool(self.enabled) - result["name"] = from_str(self.name) - result["source"] = from_str(self.source) - result["userInvocable"] = from_bool(self.user_invocable) - if self.path is not None: - result["path"] = from_union([from_str, from_none], self.path) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillsEnableRequest: - name: str - """Name of the skill to enable""" +class SessionFSRenameRequest: + dest: str + """Destination path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + src: str + """Source path using SessionFs conventions""" @staticmethod - def from_dict(obj: Any) -> 'SkillsEnableRequest': + def from_dict(obj: Any) -> 'SessionFSRenameRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return SkillsEnableRequest(name) + dest = from_str(obj.get("dest")) + session_id = from_str(obj.get("sessionId")) + src = from_str(obj.get("src")) + return SessionFSRenameRequest(dest, session_id, src) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["dest"] = from_str(self.dest) + result["sessionId"] = from_str(self.session_id) + result["src"] = from_str(self.src) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class SkillsDisableRequest: - name: str - """Name of the skill to disable""" +class SessionFSRmRequest: + path: str + """Path using SessionFs conventions""" + + session_id: str + """Target session identifier""" + + force: bool | None = None + """Ignore errors if the path does not exist""" + + recursive: bool | None = None + """Remove directories and their contents recursively""" @staticmethod - def from_dict(obj: Any) -> 'SkillsDisableRequest': + def from_dict(obj: Any) -> 'SessionFSRmRequest': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - return SkillsDisableRequest(name) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + force = from_union([from_bool, from_none], obj.get("force")) + recursive = from_union([from_bool, from_none], obj.get("recursive")) + return SessionFSRmRequest(path, session_id, force, recursive) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.force is not None: + result["force"] = from_union([from_bool, from_none], self.force) + if self.recursive is not None: + result["recursive"] = from_union([from_bool, from_none], self.recursive) return result -@dataclass -class MCPEnableRequest: - server_name: str - """Name of the MCP server to enable""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPEnableRequest': - assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - return MCPEnableRequest(server_name) +class SessionFSSetProviderConventions(Enum): + """Path conventions used by this filesystem""" - def to_dict(self) -> dict: - result: dict = {} - result["serverName"] = from_str(self.server_name) - return result + POSIX = "posix" + WINDOWS = "windows" @dataclass -class MCPDisableRequest: - server_name: str - """Name of the MCP server to disable""" +class SessionFSSetProviderResult: + success: bool + """Whether the provider was set successfully""" @staticmethod - def from_dict(obj: Any) -> 'MCPDisableRequest': + def from_dict(obj: Any) -> 'SessionFSSetProviderResult': assert isinstance(obj, dict) - server_name = from_str(obj.get("serverName")) - return MCPDisableRequest(server_name) + success = from_bool(obj.get("success")) + return SessionFSSetProviderResult(success) def to_dict(self) -> dict: result: dict = {} - result["serverName"] = from_str(self.server_name) + result["success"] = from_bool(self.success) return result @dataclass -class Plugin: - enabled: bool - """Whether the plugin is currently enabled""" - - marketplace: str - """Marketplace the plugin came from""" - - name: str - """Plugin name""" - - version: str | None = None - """Installed version""" - - @staticmethod - def from_dict(obj: Any) -> 'Plugin': - assert isinstance(obj, dict) - enabled = from_bool(obj.get("enabled")) - marketplace = from_str(obj.get("marketplace")) - name = from_str(obj.get("name")) - version = from_union([from_str, from_none], obj.get("version")) - return Plugin(enabled, marketplace, name, version) - - def to_dict(self) -> dict: - result: dict = {} - result["enabled"] = from_bool(self.enabled) - result["marketplace"] = from_str(self.marketplace) - result["name"] = from_str(self.name) - if self.version is not None: - result["version"] = from_union([from_str, from_none], self.version) - return result - -class ExtensionSource(Enum): - """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" - - PROJECT = "project" - USER = "user" - -class ExtensionStatus(Enum): - """Current status: running, disabled, failed, or starting""" - - DISABLED = "disabled" - FAILED = "failed" - RUNNING = "running" - STARTING = "starting" +class SessionFSStatRequest: + path: str + """Path using SessionFs conventions""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ExtensionsEnableRequest: - id: str - """Source-qualified extension ID to enable""" + session_id: str + """Target session identifier""" @staticmethod - def from_dict(obj: Any) -> 'ExtensionsEnableRequest': + def from_dict(obj: Any) -> 'SessionFSStatRequest': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return ExtensionsEnableRequest(id) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + return SessionFSStatRequest(path, session_id) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ExtensionsDisableRequest: - id: str - """Source-qualified extension ID to disable""" +class SessionFSWriteFileRequest: + content: str + """Content to write""" - @staticmethod - def from_dict(obj: Any) -> 'ExtensionsDisableRequest': - assert isinstance(obj, dict) - id = from_str(obj.get("id")) - return ExtensionsDisableRequest(id) + path: str + """Path using SessionFs conventions""" - def to_dict(self) -> dict: - result: dict = {} - result["id"] = from_str(self.id) - return result + session_id: str + """Target session identifier""" -@dataclass -class CommandsHandlePendingCommandResult: - success: bool - """Whether the command was handled successfully""" + mode: int | None = None + """Optional POSIX-style mode for newly created files""" @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandResult': + def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': assert isinstance(obj, dict) - success = from_bool(obj.get("success")) - return CommandsHandlePendingCommandResult(success) + content = from_str(obj.get("content")) + path = from_str(obj.get("path")) + session_id = from_str(obj.get("sessionId")) + mode = from_union([from_int, from_none], obj.get("mode")) + return SessionFSWriteFileRequest(content, path, session_id, mode) def to_dict(self) -> dict: result: dict = {} - result["success"] = from_bool(self.success) + result["content"] = from_str(self.content) + result["path"] = from_str(self.path) + result["sessionId"] = from_str(self.session_id) + if self.mode is not None: + result["mode"] = from_union([from_int, from_none], self.mode) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CommandsHandlePendingCommandRequest: - request_id: str - """Request ID from the command invocation event""" - - error: str | None = None - """Error message if the command handler failed""" - - @staticmethod - def from_dict(obj: Any) -> 'CommandsHandlePendingCommandRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - return CommandsHandlePendingCommandRequest(request_id, error) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - return result - -class UIElicitationSchemaPropertyStringFormat(Enum): - DATE = "date" - DATE_TIME = "date-time" - EMAIL = "email" - URI = "uri" - -class UIElicitationSchemaPropertyNumberType(Enum): - ARRAY = "array" - BOOLEAN = "boolean" - INTEGER = "integer" - NUMBER = "number" - STRING = "string" - -class RequestedSchemaType(Enum): - OBJECT = "object" +class SessionsForkRequest: + session_id: str + """Source session ID to fork from""" -@dataclass -class LogResult: - event_id: UUID - """The unique identifier of the emitted session event""" + to_event_id: str | None = None + """Optional event ID boundary. When provided, the fork includes only events before this ID + (exclusive). When omitted, all events are included. + """ @staticmethod - def from_dict(obj: Any) -> 'LogResult': + def from_dict(obj: Any) -> 'SessionsForkRequest': assert isinstance(obj, dict) - event_id = UUID(obj.get("eventId")) - return LogResult(event_id) + session_id = from_str(obj.get("sessionId")) + to_event_id = from_union([from_str, from_none], obj.get("toEventId")) + return SessionsForkRequest(session_id, to_event_id) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = str(self.event_id) + result["sessionId"] = from_str(self.session_id) + if self.to_event_id is not None: + result["toEventId"] = from_union([from_str, from_none], self.to_event_id) return result -class SessionLogLevel(Enum): - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - ERROR = "error" - INFO = "info" - WARNING = "warning" - +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ShellExecResult: - process_id: str - """Unique identifier for tracking streamed output""" +class SessionsForkResult: + session_id: str + """The new forked session's ID""" @staticmethod - def from_dict(obj: Any) -> 'ShellExecResult': + def from_dict(obj: Any) -> 'SessionsForkResult': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - return ShellExecResult(process_id) + session_id = from_str(obj.get("sessionId")) + return SessionsForkResult(session_id) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) + result["sessionId"] = from_str(self.session_id) return result @dataclass @@ -1342,19 +1340,19 @@ def to_dict(self) -> dict: return result @dataclass -class ShellKillResult: - killed: bool - """Whether the signal was sent successfully""" +class ShellExecResult: + process_id: str + """Unique identifier for tracking streamed output""" @staticmethod - def from_dict(obj: Any) -> 'ShellKillResult': + def from_dict(obj: Any) -> 'ShellExecResult': assert isinstance(obj, dict) - killed = from_bool(obj.get("killed")) - return ShellKillResult(killed) + process_id = from_str(obj.get("processId")) + return ShellExecResult(process_id) def to_dict(self) -> dict: result: dict = {} - result["killed"] = from_bool(self.killed) + result["processId"] = from_str(self.process_id) return result class ShellKillSignal(Enum): @@ -1365,602 +1363,570 @@ class ShellKillSignal(Enum): SIGTERM = "SIGTERM" @dataclass -class HistoryCompactContextWindow: - """Post-compaction context window usage breakdown""" +class ShellKillResult: + killed: bool + """Whether the signal was sent successfully""" - current_tokens: int - """Current total tokens in the context window (system + conversation + tool definitions)""" + @staticmethod + def from_dict(obj: Any) -> 'ShellKillResult': + assert isinstance(obj, dict) + killed = from_bool(obj.get("killed")) + return ShellKillResult(killed) - messages_length: int - """Current number of messages in the conversation""" + def to_dict(self) -> dict: + result: dict = {} + result["killed"] = from_bool(self.killed) + return result - token_limit: int - """Maximum token count for the model's context window""" +@dataclass +class Skill: + description: str + """Description of what the skill does""" - conversation_tokens: int | None = None - """Token count from non-system messages (user, assistant, tool)""" + enabled: bool + """Whether the skill is currently enabled""" - system_tokens: int | None = None - """Token count from system message(s)""" + name: str + """Unique identifier for the skill""" - tool_definitions_tokens: int | None = None - """Token count from tool definitions""" + source: str + """Source location type (e.g., project, personal, plugin)""" + + user_invocable: bool + """Whether the skill can be invoked by the user as a slash command""" + + path: str | None = None + """Absolute path to the skill file""" @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactContextWindow': + def from_dict(obj: Any) -> 'Skill': assert isinstance(obj, dict) - current_tokens = from_int(obj.get("currentTokens")) - messages_length = from_int(obj.get("messagesLength")) - token_limit = from_int(obj.get("tokenLimit")) - conversation_tokens = from_union([from_int, from_none], obj.get("conversationTokens")) - system_tokens = from_union([from_int, from_none], obj.get("systemTokens")) - tool_definitions_tokens = from_union([from_int, from_none], obj.get("toolDefinitionsTokens")) - return HistoryCompactContextWindow(current_tokens, messages_length, token_limit, conversation_tokens, system_tokens, tool_definitions_tokens) + description = from_str(obj.get("description")) + enabled = from_bool(obj.get("enabled")) + name = from_str(obj.get("name")) + source = from_str(obj.get("source")) + user_invocable = from_bool(obj.get("userInvocable")) + path = from_union([from_str, from_none], obj.get("path")) + return Skill(description, enabled, name, source, user_invocable, path) def to_dict(self) -> dict: result: dict = {} - result["currentTokens"] = from_int(self.current_tokens) - result["messagesLength"] = from_int(self.messages_length) - result["tokenLimit"] = from_int(self.token_limit) - if self.conversation_tokens is not None: - result["conversationTokens"] = from_union([from_int, from_none], self.conversation_tokens) - if self.system_tokens is not None: - result["systemTokens"] = from_union([from_int, from_none], self.system_tokens) - if self.tool_definitions_tokens is not None: - result["toolDefinitionsTokens"] = from_union([from_int, from_none], self.tool_definitions_tokens) + result["description"] = from_str(self.description) + result["enabled"] = from_bool(self.enabled) + result["name"] = from_str(self.name) + result["source"] = from_str(self.source) + result["userInvocable"] = from_bool(self.user_invocable) + if self.path is not None: + result["path"] = from_union([from_str, from_none], self.path) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateResult: - events_removed: int - """Number of events that were removed""" +class SkillsConfigSetDisabledSkillsRequest: + disabled_skills: list[str] + """List of skill names to disable""" @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateResult': + def from_dict(obj: Any) -> 'SkillsConfigSetDisabledSkillsRequest': assert isinstance(obj, dict) - events_removed = from_int(obj.get("eventsRemoved")) - return HistoryTruncateResult(events_removed) + disabled_skills = from_list(from_str, obj.get("disabledSkills")) + return SkillsConfigSetDisabledSkillsRequest(disabled_skills) def to_dict(self) -> dict: result: dict = {} - result["eventsRemoved"] = from_int(self.events_removed) + result["disabledSkills"] = from_list(from_str, self.disabled_skills) return result # Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryTruncateRequest: - event_id: str - """Event ID to truncate to. This event and all events after it are removed from the session.""" +class SkillsDisableRequest: + name: str + """Name of the skill to disable""" @staticmethod - def from_dict(obj: Any) -> 'HistoryTruncateRequest': + def from_dict(obj: Any) -> 'SkillsDisableRequest': assert isinstance(obj, dict) - event_id = from_str(obj.get("eventId")) - return HistoryTruncateRequest(event_id) + name = from_str(obj.get("name")) + return SkillsDisableRequest(name) def to_dict(self) -> dict: result: dict = {} - result["eventId"] = from_str(self.event_id) + result["name"] = from_str(self.name) return result @dataclass -class UsageMetricsCodeChanges: - """Aggregated code change metrics""" - - files_modified_count: int - """Number of distinct files modified""" - - lines_added: int - """Total lines of code added""" +class SkillsDiscoverRequest: + project_paths: list[str] | None = None + """Optional list of project directory paths to scan for project-scoped skills""" - lines_removed: int - """Total lines of code removed""" + skill_directories: list[str] | None = None + """Optional list of additional skill directory paths to include""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': + def from_dict(obj: Any) -> 'SkillsDiscoverRequest': assert isinstance(obj, dict) - files_modified_count = from_int(obj.get("filesModifiedCount")) - lines_added = from_int(obj.get("linesAdded")) - lines_removed = from_int(obj.get("linesRemoved")) - return UsageMetricsCodeChanges(files_modified_count, lines_added, lines_removed) + project_paths = from_union([lambda x: from_list(from_str, x), from_none], obj.get("projectPaths")) + skill_directories = from_union([lambda x: from_list(from_str, x), from_none], obj.get("skillDirectories")) + return SkillsDiscoverRequest(project_paths, skill_directories) def to_dict(self) -> dict: result: dict = {} - result["filesModifiedCount"] = from_int(self.files_modified_count) - result["linesAdded"] = from_int(self.lines_added) - result["linesRemoved"] = from_int(self.lines_removed) + if self.project_paths is not None: + result["projectPaths"] = from_union([lambda x: from_list(from_str, x), from_none], self.project_paths) + if self.skill_directories is not None: + result["skillDirectories"] = from_union([lambda x: from_list(from_str, x), from_none], self.skill_directories) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class UsageMetricsModelMetricRequests: - """Request count and cost metrics for this model""" - - cost: float - """User-initiated premium request cost (with multiplier applied)""" - - count: int - """Number of API requests made with this model""" +class SkillsEnableRequest: + name: str + """Name of the skill to enable""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsModelMetricRequests': + def from_dict(obj: Any) -> 'SkillsEnableRequest': assert isinstance(obj, dict) - cost = from_float(obj.get("cost")) - count = from_int(obj.get("count")) - return UsageMetricsModelMetricRequests(cost, count) + name = from_str(obj.get("name")) + return SkillsEnableRequest(name) def to_dict(self) -> dict: result: dict = {} - result["cost"] = to_float(self.cost) - result["count"] = from_int(self.count) + result["name"] = from_str(self.name) return result @dataclass -class UsageMetricsModelMetricUsage: - """Token usage metrics for this model""" - - cache_read_tokens: int - """Total tokens read from prompt cache""" - - cache_write_tokens: int - """Total tokens written to prompt cache""" +class Tool: + description: str + """Description of what the tool does""" - input_tokens: int - """Total input tokens consumed""" + name: str + """Tool identifier (e.g., "bash", "grep", "str_replace_editor")""" - output_tokens: int - """Total output tokens produced""" + instructions: str | None = None + """Optional instructions for how to use this tool effectively""" - reasoning_tokens: int | None = None - """Total output tokens used for reasoning""" + namespaced_name: str | None = None + """Optional namespaced name for declarative filtering (e.g., "playwright/navigate" for MCP + tools) + """ + parameters: dict[str, Any] | None = None + """JSON Schema for the tool's input parameters""" @staticmethod - def from_dict(obj: Any) -> 'UsageMetricsModelMetricUsage': + def from_dict(obj: Any) -> 'Tool': assert isinstance(obj, dict) - cache_read_tokens = from_int(obj.get("cacheReadTokens")) - cache_write_tokens = from_int(obj.get("cacheWriteTokens")) - input_tokens = from_int(obj.get("inputTokens")) - output_tokens = from_int(obj.get("outputTokens")) - reasoning_tokens = from_union([from_int, from_none], obj.get("reasoningTokens")) - return UsageMetricsModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) + description = from_str(obj.get("description")) + name = from_str(obj.get("name")) + instructions = from_union([from_str, from_none], obj.get("instructions")) + namespaced_name = from_union([from_str, from_none], obj.get("namespacedName")) + parameters = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("parameters")) + return Tool(description, name, instructions, namespaced_name, parameters) def to_dict(self) -> dict: result: dict = {} - result["cacheReadTokens"] = from_int(self.cache_read_tokens) - result["cacheWriteTokens"] = from_int(self.cache_write_tokens) - result["inputTokens"] = from_int(self.input_tokens) - result["outputTokens"] = from_int(self.output_tokens) - if self.reasoning_tokens is not None: - result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) + result["description"] = from_str(self.description) + result["name"] = from_str(self.name) + if self.instructions is not None: + result["instructions"] = from_union([from_str, from_none], self.instructions) + if self.namespaced_name is not None: + result["namespacedName"] = from_union([from_str, from_none], self.namespaced_name) + if self.parameters is not None: + result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result @dataclass -class SessionFSReadFileRequest: - path: str - """Path using SessionFs conventions""" +class ToolCallResult: + text_result_for_llm: str + """Text result to send back to the LLM""" - session_id: str - """Target session identifier""" + error: str | None = None + """Error message if the tool call failed""" + + result_type: str | None = None + """Type of the tool result""" + + tool_telemetry: dict[str, Any] | None = None + """Telemetry data from tool execution""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileRequest': + def from_dict(obj: Any) -> 'ToolCallResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReadFileRequest(path, session_id) + text_result_for_llm = from_str(obj.get("textResultForLlm")) + error = from_union([from_str, from_none], obj.get("error")) + result_type = from_union([from_str, from_none], obj.get("resultType")) + tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) + return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["textResultForLlm"] = from_str(self.text_result_for_llm) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result_type is not None: + result["resultType"] = from_union([from_str, from_none], self.result_type) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) return result @dataclass -class SessionFSWriteFileRequest: - content: str - """Content to write""" - - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - mode: int | None = None - """Optional POSIX-style mode for newly created files""" +class ToolsListRequest: + model: str | None = None + """Optional model ID — when provided, the returned tool list reflects model-specific + overrides + """ @staticmethod - def from_dict(obj: Any) -> 'SessionFSWriteFileRequest': + def from_dict(obj: Any) -> 'ToolsListRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSWriteFileRequest(content, path, session_id, mode) + model = from_union([from_str, from_none], obj.get("model")) + return ToolsListRequest(model) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) + if self.model is not None: + result["model"] = from_union([from_str, from_none], self.model) return result @dataclass -class SessionFSAppendFileRequest: - content: str - """Content to append""" +class UIElicitationArrayAnyOfFieldItemsAnyOf: + const: str + title: str - path: str - """Path using SessionFs conventions""" + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItemsAnyOf': + assert isinstance(obj, dict) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationArrayAnyOfFieldItemsAnyOf(const, title) - session_id: str - """Target session identifier""" + def to_dict(self) -> dict: + result: dict = {} + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) + return result - mode: int | None = None - """Optional POSIX-style mode for newly created files""" +class UIElicitationArrayAnyOfFieldType(Enum): + ARRAY = "array" + +class UIElicitationArrayEnumFieldItemsType(Enum): + STRING = "string" + +class UIElicitationSchemaPropertyStringFormat(Enum): + DATE = "date" + DATE_TIME = "date-time" + EMAIL = "email" + URI = "uri" + +@dataclass +class UIElicitationStringOneOfFieldOneOf: + const: str + title: str @staticmethod - def from_dict(obj: Any) -> 'SessionFSAppendFileRequest': + def from_dict(obj: Any) -> 'UIElicitationStringOneOfFieldOneOf': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - return SessionFSAppendFileRequest(content, path, session_id, mode) + const = from_str(obj.get("const")) + title = from_str(obj.get("title")) + return UIElicitationStringOneOfFieldOneOf(const, title) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) + result["const"] = from_str(self.const) + result["title"] = from_str(self.title) return result +class UIElicitationSchemaPropertyType(Enum): + ARRAY = "array" + BOOLEAN = "boolean" + INTEGER = "integer" + NUMBER = "number" + STRING = "string" + +class UIElicitationSchemaType(Enum): + OBJECT = "object" + +class UIElicitationResponseAction(Enum): + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + + ACCEPT = "accept" + CANCEL = "cancel" + DECLINE = "decline" + @dataclass -class SessionFSExistsResult: - exists: bool - """Whether the path exists""" +class UIElicitationResult: + success: bool + """Whether the response was accepted. False if the request was already resolved by another + client. + """ @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsResult': + def from_dict(obj: Any) -> 'UIElicitationResult': assert isinstance(obj, dict) - exists = from_bool(obj.get("exists")) - return SessionFSExistsResult(exists) + success = from_bool(obj.get("success")) + return UIElicitationResult(success) def to_dict(self) -> dict: result: dict = {} - result["exists"] = from_bool(self.exists) + result["success"] = from_bool(self.success) return result +class UIElicitationSchemaPropertyBooleanType(Enum): + BOOLEAN = "boolean" + +class UIElicitationSchemaPropertyNumberType(Enum): + INTEGER = "integer" + NUMBER = "number" + @dataclass -class SessionFSExistsRequest: - path: str - """Path using SessionFs conventions""" +class UsageMetricsCodeChanges: + """Aggregated code change metrics""" - session_id: str - """Target session identifier""" + files_modified_count: int + """Number of distinct files modified""" + + lines_added: int + """Total lines of code added""" + + lines_removed: int + """Total lines of code removed""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSExistsRequest': + def from_dict(obj: Any) -> 'UsageMetricsCodeChanges': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSExistsRequest(path, session_id) + files_modified_count = from_int(obj.get("filesModifiedCount")) + lines_added = from_int(obj.get("linesAdded")) + lines_removed = from_int(obj.get("linesRemoved")) + return UsageMetricsCodeChanges(files_modified_count, lines_added, lines_removed) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["filesModifiedCount"] = from_int(self.files_modified_count) + result["linesAdded"] = from_int(self.lines_added) + result["linesRemoved"] = from_int(self.lines_removed) return result @dataclass -class SessionFSStatRequest: - path: str - """Path using SessionFs conventions""" +class UsageMetricsModelMetricRequests: + """Request count and cost metrics for this model""" - session_id: str - """Target session identifier""" + cost: float + """User-initiated premium request cost (with multiplier applied)""" + + count: int + """Number of API requests made with this model""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatRequest': + def from_dict(obj: Any) -> 'UsageMetricsModelMetricRequests': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSStatRequest(path, session_id) + cost = from_float(obj.get("cost")) + count = from_int(obj.get("count")) + return UsageMetricsModelMetricRequests(cost, count) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["cost"] = to_float(self.cost) + result["count"] = from_int(self.count) return result @dataclass -class SessionFSMkdirRequest: - path: str - """Path using SessionFs conventions""" +class UsageMetricsModelMetricUsage: + """Token usage metrics for this model""" - session_id: str - """Target session identifier""" + cache_read_tokens: int + """Total tokens read from prompt cache""" - mode: int | None = None - """Optional POSIX-style mode for newly created directories""" + cache_write_tokens: int + """Total tokens written to prompt cache""" - recursive: bool | None = None - """Create parent directories as needed""" + input_tokens: int + """Total input tokens consumed""" + + output_tokens: int + """Total output tokens produced""" + + reasoning_tokens: int | None = None + """Total output tokens used for reasoning""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSMkdirRequest': + def from_dict(obj: Any) -> 'UsageMetricsModelMetricUsage': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - mode = from_union([from_int, from_none], obj.get("mode")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSMkdirRequest(path, session_id, mode, recursive) + cache_read_tokens = from_int(obj.get("cacheReadTokens")) + cache_write_tokens = from_int(obj.get("cacheWriteTokens")) + input_tokens = from_int(obj.get("inputTokens")) + output_tokens = from_int(obj.get("outputTokens")) + reasoning_tokens = from_union([from_int, from_none], obj.get("reasoningTokens")) + return UsageMetricsModelMetricUsage(cache_read_tokens, cache_write_tokens, input_tokens, output_tokens, reasoning_tokens) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.mode is not None: - result["mode"] = from_union([from_int, from_none], self.mode) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) + result["cacheReadTokens"] = from_int(self.cache_read_tokens) + result["cacheWriteTokens"] = from_int(self.cache_write_tokens) + result["inputTokens"] = from_int(self.input_tokens) + result["outputTokens"] = from_int(self.output_tokens) + if self.reasoning_tokens is not None: + result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) return result @dataclass -class SessionFSReaddirRequest: - path: str - """Path using SessionFs conventions""" +class WorkspacesCreateFileRequest: + content: str + """File content to write as a UTF-8 string""" - session_id: str - """Target session identifier""" + path: str + """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirRequest': + def from_dict(obj: Any) -> 'WorkspacesCreateFileRequest': assert isinstance(obj, dict) + content = from_str(obj.get("content")) path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirRequest(path, session_id) + return WorkspacesCreateFileRequest(content, path) def to_dict(self) -> dict: result: dict = {} + result["content"] = from_str(self.content) result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) return result -class SessionFSReaddirWithTypesEntryType(Enum): - """Entry type""" +class HostType(Enum): + ADO = "ado" + GITHUB = "github" - DIRECTORY = "directory" - FILE = "file" +class SessionSyncLevel(Enum): + LOCAL = "local" + REPO_AND_USER = "repo_and_user" + USER = "user" @dataclass -class SessionFSReaddirWithTypesRequest: - path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" +class WorkspacesListFilesResult: + files: list[str] + """Relative file paths in the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesRequest': + def from_dict(obj: Any) -> 'WorkspacesListFilesResult': assert isinstance(obj, dict) - path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - return SessionFSReaddirWithTypesRequest(path, session_id) + files = from_list(from_str, obj.get("files")) + return WorkspacesListFilesResult(files) def to_dict(self) -> dict: result: dict = {} - result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) + result["files"] = from_list(from_str, self.files) return result @dataclass -class SessionFSRmRequest: +class WorkspacesReadFileRequest: path: str - """Path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - force: bool | None = None - """Ignore errors if the path does not exist""" - - recursive: bool | None = None - """Remove directories and their contents recursively""" + """Relative path within the workspace files directory""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSRmRequest': + def from_dict(obj: Any) -> 'WorkspacesReadFileRequest': assert isinstance(obj, dict) path = from_str(obj.get("path")) - session_id = from_str(obj.get("sessionId")) - force = from_union([from_bool, from_none], obj.get("force")) - recursive = from_union([from_bool, from_none], obj.get("recursive")) - return SessionFSRmRequest(path, session_id, force, recursive) + return WorkspacesReadFileRequest(path) def to_dict(self) -> dict: result: dict = {} result["path"] = from_str(self.path) - result["sessionId"] = from_str(self.session_id) - if self.force is not None: - result["force"] = from_union([from_bool, from_none], self.force) - if self.recursive is not None: - result["recursive"] = from_union([from_bool, from_none], self.recursive) return result @dataclass -class SessionFSRenameRequest: - dest: str - """Destination path using SessionFs conventions""" - - session_id: str - """Target session identifier""" - - src: str - """Source path using SessionFs conventions""" +class WorkspacesReadFileResult: + content: str + """File content as a UTF-8 string""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSRenameRequest': + def from_dict(obj: Any) -> 'WorkspacesReadFileResult': assert isinstance(obj, dict) - dest = from_str(obj.get("dest")) - session_id = from_str(obj.get("sessionId")) - src = from_str(obj.get("src")) - return SessionFSRenameRequest(dest, session_id, src) + content = from_str(obj.get("content")) + return WorkspacesReadFileResult(content) def to_dict(self) -> dict: result: dict = {} - result["dest"] = from_str(self.dest) - result["sessionId"] = from_str(self.session_id) - result["src"] = from_str(self.src) + result["content"] = from_str(self.content) return result @dataclass -class ModelCapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" - - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" - - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" - - vision: ModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" +class AccountGetQuotaResult: + quota_snapshots: dict[str, AccountQuotaSnapshot] + """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': + def from_dict(obj: Any) -> 'AccountGetQuotaResult': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) + return AccountGetQuotaResult(quota_snapshots) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) + result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class CapabilitiesLimits: - """Token limits for prompts, outputs, and context window""" - - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" +class AgentGetCurrentResult: + agent: AgentInfo | None = None + """Currently selected custom agent, or null if using the default agent""" - max_output_tokens: int | None = None - """Maximum number of output/completion tokens""" + @staticmethod + def from_dict(obj: Any) -> 'AgentGetCurrentResult': + assert isinstance(obj, dict) + agent = from_union([AgentInfo.from_dict, from_none], obj.get("agent")) + return AgentGetCurrentResult(agent) - max_prompt_tokens: int | None = None - """Maximum number of prompt/input tokens""" + def to_dict(self) -> dict: + result: dict = {} + if self.agent is not None: + result["agent"] = from_union([lambda x: to_class(AgentInfo, x), from_none], self.agent) + return result - vision: ModelCapabilitiesLimitsVision | None = None - """Vision-specific limits""" +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentList: + agents: list[AgentInfo] + """Available custom agents""" @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesLimits': + def from_dict(obj: Any) -> 'AgentList': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) - return CapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentList(agents) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPServerConfig: - """MCP server configuration (local/stdio or remote/http)""" - - args: list[str] | None = None - command: str | None = None - cwd: str | None = None - env: dict[str, str] | None = None - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - is_default_server: bool | None = None - timeout: int | None = None - """Timeout in milliseconds for tool calls to this server.""" +class AgentReloadResult: + agents: list[AgentInfo] + """Reloaded custom agents""" - tools: list[str] | None = None - """Tools to include. Defaults to all tools if not specified.""" + @staticmethod + def from_dict(obj: Any) -> 'AgentReloadResult': + assert isinstance(obj, dict) + agents = from_list(AgentInfo.from_dict, obj.get("agents")) + return AgentReloadResult(agents) - type: MCPServerConfigType | None = None - """Remote transport type. Defaults to "http" when omitted.""" + def to_dict(self) -> dict: + result: dict = {} + result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) + return result - headers: dict[str, str] | None = None - oauth_client_id: str | None = None - oauth_public_client: bool | None = None - url: str | None = None +# Experimental: this type is part of an experimental API and may change or be removed. +@dataclass +class AgentSelectResult: + agent: AgentInfo + """The newly selected custom agent""" @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfig': + def from_dict(obj: Any) -> 'AgentSelectResult': assert isinstance(obj, dict) - args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) - command = from_union([from_str, from_none], obj.get("command")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) - is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) - timeout = from_union([from_int, from_none], obj.get("timeout")) - tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) - type = from_union([MCPServerConfigType, from_none], obj.get("type")) - headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) - oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) - oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) - url = from_union([from_str, from_none], obj.get("url")) - return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) + agent = AgentInfo.from_dict(obj.get("agent")) + return AgentSelectResult(agent) def to_dict(self) -> dict: result: dict = {} - if self.args is not None: - result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) - if self.command is not None: - result["command"] = from_union([from_str, from_none], self.command) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.env is not None: - result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) - if self.filter_mapping is not None: - result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) - if self.is_default_server is not None: - result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) - if self.timeout is not None: - result["timeout"] = from_union([from_int, from_none], self.timeout) - if self.tools is not None: - result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) - if self.headers is not None: - result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) - if self.oauth_client_id is not None: - result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) - if self.oauth_public_client is not None: - result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + result["agent"] = to_class(AgentInfo, self.agent) return result @dataclass @@ -1996,135 +1962,233 @@ def to_dict(self) -> dict: return result @dataclass -class ServerSkillList: - skills: list[ServerSkill] - """All discovered skills across all sources""" +class Extension: + id: str + """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" + + name: str + """Extension name (directory name)""" + + source: ExtensionSource + """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" + + status: ExtensionStatus + """Current status: running, disabled, failed, or starting""" + + pid: int | None = None + """Process ID if the extension is running""" @staticmethod - def from_dict(obj: Any) -> 'ServerSkillList': + def from_dict(obj: Any) -> 'Extension': assert isinstance(obj, dict) - skills = from_list(ServerSkill.from_dict, obj.get("skills")) - return ServerSkillList(skills) + id = from_str(obj.get("id")) + name = from_str(obj.get("name")) + source = ExtensionSource(obj.get("source")) + status = ExtensionStatus(obj.get("status")) + pid = from_union([from_int, from_none], obj.get("pid")) + return Extension(id, name, source, status, pid) def to_dict(self) -> dict: result: dict = {} - result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) + result["id"] = from_str(self.id) + result["name"] = from_str(self.name) + result["source"] = to_enum(ExtensionSource, self.source) + result["status"] = to_enum(ExtensionStatus, self.status) + if self.pid is not None: + result["pid"] = from_union([from_int, from_none], self.pid) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModelCapabilitiesOverrideLimits: - """Token limits for prompts, outputs, and context window""" +class HistoryCompactResult: + messages_removed: int + """Number of messages removed during compaction""" - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" + success: bool + """Whether compaction completed successfully""" - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None + tokens_removed: int + """Number of tokens freed by compaction""" + + context_window: HistoryCompactContextWindow | None = None + """Post-compaction context window usage breakdown""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': + def from_dict(obj: Any) -> 'HistoryCompactResult': assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + messages_removed = from_int(obj.get("messagesRemoved")) + success = from_bool(obj.get("success")) + tokens_removed = from_int(obj.get("tokensRemoved")) + context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) + return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) def to_dict(self) -> dict: result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) + result["messagesRemoved"] = from_int(self.messages_removed) + result["success"] = from_bool(self.success) + result["tokensRemoved"] = from_int(self.tokens_removed) + if self.context_window is not None: + result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) return result @dataclass -class ModelCapabilitiesLimitsClass: - """Token limits for prompts, outputs, and context window""" +class InstructionsSources: + content: str + """Raw content of the instruction file""" - max_context_window_tokens: int | None = None - """Maximum total context window size in tokens""" + id: str + """Unique identifier for this source (used for toggling)""" - max_output_tokens: int | None = None - max_prompt_tokens: int | None = None - vision: PurpleModelCapabilitiesOverrideLimitsVision | None = None + label: str + """Human-readable label""" - @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesLimitsClass': - assert isinstance(obj, dict) - max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) - max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) - max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) - vision = from_union([PurpleModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) - return ModelCapabilitiesLimitsClass(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) + location: InstructionsSourcesLocation + """Where this source lives — used for UI grouping""" - def to_dict(self) -> dict: - result: dict = {} - if self.max_context_window_tokens is not None: - result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) - if self.max_output_tokens is not None: - result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) - if self.max_prompt_tokens is not None: - result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) - if self.vision is not None: - result["vision"] = from_union([lambda x: to_class(PurpleModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) - return result + source_path: str + """File path relative to repo or absolute for home""" -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class AgentList: - agents: list[AgentInfo] - """Available custom agents""" + type: InstructionsSourcesType + """Category of instruction source — used for merge logic""" + + apply_to: str | None = None + """Glob pattern from frontmatter — when set, this instruction applies only to matching files""" + + description: str | None = None + """Short description (body after frontmatter) for use in instruction tables""" @staticmethod - def from_dict(obj: Any) -> 'AgentList': + def from_dict(obj: Any) -> 'InstructionsSources': assert isinstance(obj, dict) - agents = from_list(AgentInfo.from_dict, obj.get("agents")) - return AgentList(agents) + content = from_str(obj.get("content")) + id = from_str(obj.get("id")) + label = from_str(obj.get("label")) + location = InstructionsSourcesLocation(obj.get("location")) + source_path = from_str(obj.get("sourcePath")) + type = InstructionsSourcesType(obj.get("type")) + apply_to = from_union([from_str, from_none], obj.get("applyTo")) + description = from_union([from_str, from_none], obj.get("description")) + return InstructionsSources(content, id, label, location, source_path, type, apply_to, description) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) + result["content"] = from_str(self.content) + result["id"] = from_str(self.id) + result["label"] = from_str(self.label) + result["location"] = to_enum(InstructionsSourcesLocation, self.location) + result["sourcePath"] = from_str(self.source_path) + result["type"] = to_enum(InstructionsSourcesType, self.type) + if self.apply_to is not None: + result["applyTo"] = from_union([from_str, from_none], self.apply_to) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentGetCurrentResult: - agent: AgentInfo | None = None - """Currently selected custom agent, or null if using the default agent""" +class LogRequest: + message: str + """Human-readable message""" + + ephemeral: bool | None = None + """When true, the message is transient and not persisted to the session event log on disk""" + + level: SessionLogLevel | None = None + """Log severity level. Determines how the message is displayed in the timeline. Defaults to + "info". + """ + url: str | None = None + """Optional URL the user can open in their browser for more details""" @staticmethod - def from_dict(obj: Any) -> 'AgentGetCurrentResult': + def from_dict(obj: Any) -> 'LogRequest': assert isinstance(obj, dict) - agent = from_union([AgentInfo.from_dict, from_none], obj.get("agent")) - return AgentGetCurrentResult(agent) + message = from_str(obj.get("message")) + ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) + level = from_union([SessionLogLevel, from_none], obj.get("level")) + url = from_union([from_str, from_none], obj.get("url")) + return LogRequest(message, ephemeral, level, url) def to_dict(self) -> dict: result: dict = {} - if self.agent is not None: - result["agent"] = from_union([lambda x: to_class(AgentInfo, x), from_none], self.agent) + result["message"] = from_str(self.message) + if self.ephemeral is not None: + result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) + if self.level is not None: + result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentReloadResult: - agents: list[AgentInfo] - """Reloaded custom agents""" +class MCPServerConfig: + """MCP server configuration (local/stdio or remote/http)""" + + args: list[str] | None = None + command: str | None = None + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + + headers: dict[str, str] | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + url: str | None = None @staticmethod - def from_dict(obj: Any) -> 'AgentReloadResult': + def from_dict(obj: Any) -> 'MCPServerConfig': assert isinstance(obj, dict) - agents = from_list(AgentInfo.from_dict, obj.get("agents")) - return AgentReloadResult(agents) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + command = from_union([from_str, from_none], obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigType, from_none], obj.get("type")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + url = from_union([from_str, from_none], obj.get("url")) + return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type, headers, oauth_client_id, oauth_public_client, url) def to_dict(self) -> dict: result: dict = {} - result["agents"] = from_list(lambda x: to_class(AgentInfo, x), self.agents) + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + if self.command is not None: + result["command"] = from_union([from_str, from_none], self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigType, x), from_none], self.type) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.url is not None: + result["url"] = from_union([from_str, from_none], self.url) return result @dataclass @@ -2161,153 +2225,195 @@ def to_dict(self) -> dict: return result @dataclass -class UIElicitationStringEnumField: - enum: list[str] - type: UIElicitationStringEnumFieldType - default: str | None = None - description: str | None = None - enum_names: list[str] | None = None - title: str | None = None +class MCPServerConfigHTTP: + url: str + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + headers: dict[str, str] | None = None + is_default_server: bool | None = None + oauth_client_id: str | None = None + oauth_public_client: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigHTTPType | None = None + """Remote transport type. Defaults to "http" when omitted.""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringEnumField': + def from_dict(obj: Any) -> 'MCPServerConfigHTTP': assert isinstance(obj, dict) - enum = from_list(from_str, obj.get("enum")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - default = from_union([from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationStringEnumField(enum, type, default, description, enum_names, title) + url = from_str(obj.get("url")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + headers = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("headers")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + oauth_client_id = from_union([from_str, from_none], obj.get("oauthClientId")) + oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) + return MCPServerConfigHTTP(url, filter_mapping, headers, is_default_server, oauth_client_id, oauth_public_client, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} - result["enum"] = from_list(from_str, self.enum) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([from_str, from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.enum_names is not None: - result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["url"] = from_str(self.url) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.headers is not None: + result["headers"] = from_union([lambda x: from_dict(from_str, x), from_none], self.headers) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.oauth_client_id is not None: + result["oauthClientId"] = from_union([from_str, from_none], self.oauth_client_id) + if self.oauth_public_client is not None: + result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) return result @dataclass -class UIElicitationArrayEnumFieldItems: - enum: list[str] - type: UIElicitationStringEnumFieldType +class MCPServerConfigLocal: + args: list[str] + command: str + cwd: str | None = None + env: dict[str, str] | None = None + filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + is_default_server: bool | None = None + timeout: int | None = None + """Timeout in milliseconds for tool calls to this server.""" + + tools: list[str] | None = None + """Tools to include. Defaults to all tools if not specified.""" + + type: MCPServerConfigLocalType | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayEnumFieldItems': + def from_dict(obj: Any) -> 'MCPServerConfigLocal': assert isinstance(obj, dict) - enum = from_list(from_str, obj.get("enum")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - return UIElicitationArrayEnumFieldItems(enum, type) - - def to_dict(self) -> dict: - result: dict = {} - result["enum"] = from_list(from_str, self.enum) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) - return result - -@dataclass -class UIElicitationStringOneOfField: - one_of: list[UIElicitationStringOneOfFieldOneOf] - type: UIElicitationStringEnumFieldType - default: str | None = None - description: str | None = None - title: str | None = None - - @staticmethod - def from_dict(obj: Any) -> 'UIElicitationStringOneOfField': - assert isinstance(obj, dict) - one_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("oneOf")) - type = UIElicitationStringEnumFieldType(obj.get("type")) - default = from_union([from_str, from_none], obj.get("default")) - description = from_union([from_str, from_none], obj.get("description")) - title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationStringOneOfField(one_of, type, default, description, title) + args = from_list(from_str, obj.get("args")) + command = from_str(obj.get("command")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + env = from_union([lambda x: from_dict(from_str, x), from_none], obj.get("env")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + is_default_server = from_union([from_bool, from_none], obj.get("isDefaultServer")) + timeout = from_union([from_int, from_none], obj.get("timeout")) + tools = from_union([lambda x: from_list(from_str, x), from_none], obj.get("tools")) + type = from_union([MCPServerConfigLocalType, from_none], obj.get("type")) + return MCPServerConfigLocal(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} - result["oneOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.one_of) - result["type"] = to_enum(UIElicitationStringEnumFieldType, self.type) - if self.default is not None: - result["default"] = from_union([from_str, from_none], self.default) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) - if self.title is not None: - result["title"] = from_union([from_str, from_none], self.title) + result["args"] = from_list(from_str, self.args) + result["command"] = from_str(self.command) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.env is not None: + result["env"] = from_union([lambda x: from_dict(from_str, x), from_none], self.env) + if self.filter_mapping is not None: + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x), from_none], self.filter_mapping) + if self.is_default_server is not None: + result["isDefaultServer"] = from_union([from_bool, from_none], self.is_default_server) + if self.timeout is not None: + result["timeout"] = from_union([from_int, from_none], self.timeout) + if self.tools is not None: + result["tools"] = from_union([lambda x: from_list(from_str, x), from_none], self.tools) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigLocalType, x), from_none], self.type) return result @dataclass -class UIElicitationArrayAnyOfFieldItems: - any_of: list[UIElicitationStringOneOfFieldOneOf] +class ModeSetRequest: + mode: SessionMode + """The agent mode. Valid values: "interactive", "plan", "autopilot".""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': + def from_dict(obj: Any) -> 'ModeSetRequest': assert isinstance(obj, dict) - any_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("anyOf")) - return UIElicitationArrayAnyOfFieldItems(any_of) + mode = SessionMode(obj.get("mode")) + return ModeSetRequest(mode) def to_dict(self) -> dict: result: dict = {} - result["anyOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.any_of) + result["mode"] = to_enum(SessionMode, self.mode) return result @dataclass -class UIElicitationArrayFieldItems: - enum: list[str] | None = None - type: UIElicitationStringEnumFieldType | None = None - any_of: list[UIElicitationStringOneOfFieldOneOf] | None = None +class ModelCapabilitiesLimits: + """Token limits for prompts, outputs, and context window""" + + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" + + max_output_tokens: int | None = None + """Maximum number of output/completion tokens""" + + max_prompt_tokens: int | None = None + """Maximum number of prompt/input tokens""" + + vision: ModelCapabilitiesLimitsVision | None = None + """Vision-specific limits""" @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': + def from_dict(obj: Any) -> 'ModelCapabilitiesLimits': assert isinstance(obj, dict) - enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) - type = from_union([UIElicitationStringEnumFieldType, from_none], obj.get("type")) - any_of = from_union([lambda x: from_list(UIElicitationStringOneOfFieldOneOf.from_dict, x), from_none], obj.get("anyOf")) - return UIElicitationArrayFieldItems(enum, type, any_of) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([ModelCapabilitiesLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - if self.enum is not None: - result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) - if self.type is not None: - result["type"] = from_union([lambda x: to_enum(UIElicitationStringEnumFieldType, x), from_none], self.type) - if self.any_of is not None: - result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), x), from_none], self.any_of) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result @dataclass -class UIElicitationResponse: - """The elicitation response (accept with form values, decline, or cancel)""" +class ModelCapabilitiesOverrideLimits: + """Token limits for prompts, outputs, and context window""" - action: UIElicitationResponseAction - """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + max_context_window_tokens: int | None = None + """Maximum total context window size in tokens""" - content: dict[str, float | bool | list[str] | str] | None = None - """The form values submitted by the user (present when action is 'accept')""" + max_output_tokens: int | None = None + max_prompt_tokens: int | None = None + vision: ModelCapabilitiesOverrideLimitsVision | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationResponse': + def from_dict(obj: Any) -> 'ModelCapabilitiesOverrideLimits': assert isinstance(obj, dict) - action = UIElicitationResponseAction(obj.get("action")) - content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) - return UIElicitationResponse(action, content) + max_context_window_tokens = from_union([from_int, from_none], obj.get("max_context_window_tokens")) + max_output_tokens = from_union([from_int, from_none], obj.get("max_output_tokens")) + max_prompt_tokens = from_union([from_int, from_none], obj.get("max_prompt_tokens")) + vision = from_union([ModelCapabilitiesOverrideLimitsVision.from_dict, from_none], obj.get("vision")) + return ModelCapabilitiesOverrideLimits(max_context_window_tokens, max_output_tokens, max_prompt_tokens, vision) def to_dict(self) -> dict: result: dict = {} - result["action"] = to_enum(UIElicitationResponseAction, self.action) - if self.content is not None: - result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) + if self.max_context_window_tokens is not None: + result["max_context_window_tokens"] = from_union([from_int, from_none], self.max_context_window_tokens) + if self.max_output_tokens is not None: + result["max_output_tokens"] = from_union([from_int, from_none], self.max_output_tokens) + if self.max_prompt_tokens is not None: + result["max_prompt_tokens"] = from_union([from_int, from_none], self.max_prompt_tokens) + if self.vision is not None: + result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimitsVision, x), from_none], self.vision) return result @dataclass class PermissionDecision: - kind: Kind + kind: PermissionDecisionKind """The permission request was approved Denied because approval rules explicitly blocked it @@ -2340,7 +2446,7 @@ class PermissionDecision: @staticmethod def from_dict(obj: Any) -> 'PermissionDecision': assert isinstance(obj, dict) - kind = Kind(obj.get("kind")) + kind = PermissionDecisionKind(obj.get("kind")) rules = from_union([lambda x: from_list(lambda x: x, x), from_none], obj.get("rules")) feedback = from_union([from_str, from_none], obj.get("feedback")) message = from_union([from_str, from_none], obj.get("message")) @@ -2350,7 +2456,7 @@ def from_dict(obj: Any) -> 'PermissionDecision': def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(Kind, self.kind) + result["kind"] = to_enum(PermissionDecisionKind, self.kind) if self.rules is not None: result["rules"] = from_union([lambda x: from_list(lambda x: x, x), from_none], self.rules) if self.feedback is not None: @@ -2364,278 +2470,258 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSError: - """Describes a filesystem error.""" - - code: SessionFSErrorCode - """Error classification""" - - message: str | None = None - """Free-form detail about the error, for logging/diagnostics""" +class PermissionDecisionApproved: + kind: PermissionDecisionApprovedKind + """The permission request was approved""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSError': + def from_dict(obj: Any) -> 'PermissionDecisionApproved': assert isinstance(obj, dict) - code = SessionFSErrorCode(obj.get("code")) - message = from_union([from_str, from_none], obj.get("message")) - return SessionFSError(code, message) + kind = PermissionDecisionApprovedKind(obj.get("kind")) + return PermissionDecisionApproved(kind) def to_dict(self) -> dict: result: dict = {} - result["code"] = to_enum(SessionFSErrorCode, self.code) - if self.message is not None: - result["message"] = from_union([from_str, from_none], self.message) + result["kind"] = to_enum(PermissionDecisionApprovedKind, self.kind) return result @dataclass -class ToolList: - tools: list[Tool] - """List of available built-in tools with metadata""" +class PermissionDecisionDeniedByContentExclusionPolicy: + kind: PermissionDecisionDeniedByContentExclusionPolicyKind + """Denied by the organization's content exclusion policy""" + + message: str + """Human-readable explanation of why the path was excluded""" + + path: str + """File path that triggered the exclusion""" @staticmethod - def from_dict(obj: Any) -> 'ToolList': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByContentExclusionPolicy': assert isinstance(obj, dict) - tools = from_list(Tool.from_dict, obj.get("tools")) - return ToolList(tools) + kind = PermissionDecisionDeniedByContentExclusionPolicyKind(obj.get("kind")) + message = from_str(obj.get("message")) + path = from_str(obj.get("path")) + return PermissionDecisionDeniedByContentExclusionPolicy(kind, message, path) def to_dict(self) -> dict: result: dict = {} - result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) + result["kind"] = to_enum(PermissionDecisionDeniedByContentExclusionPolicyKind, self.kind) + result["message"] = from_str(self.message) + result["path"] = from_str(self.path) return result @dataclass -class ToolsHandlePendingToolCallRequest: - request_id: str - """Request ID of the pending tool call""" +class PermissionDecisionDeniedByPermissionRequestHook: + kind: PermissionDecisionDeniedByPermissionRequestHookKind + """Denied by a permission request hook registered by an extension or plugin""" - error: str | None = None - """Error message if the tool call failed""" + interrupt: bool | None = None + """Whether to interrupt the current agent turn""" - result: ToolCallResult | str | None = None - """Tool call result (string or expanded result object)""" + message: str | None = None + """Optional message from the hook explaining the denial""" @staticmethod - def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByPermissionRequestHook': assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) - return ToolsHandlePendingToolCallRequest(request_id, error, result) + kind = PermissionDecisionDeniedByPermissionRequestHookKind(obj.get("kind")) + interrupt = from_union([from_bool, from_none], obj.get("interrupt")) + message = from_union([from_str, from_none], obj.get("message")) + return PermissionDecisionDeniedByPermissionRequestHook(kind, interrupt, message) def to_dict(self) -> dict: result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + result["kind"] = to_enum(PermissionDecisionDeniedByPermissionRequestHookKind, self.kind) + if self.interrupt is not None: + result["interrupt"] = from_union([from_bool, from_none], self.interrupt) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) return result @dataclass -class AccountGetQuotaResult: - quota_snapshots: dict[str, AccountQuotaSnapshot] - """Quota snapshots keyed by type (e.g., chat, completions, premium_interactions)""" +class PermissionDecisionDeniedByRules: + kind: PermissionDecisionDeniedByRulesKind + """Denied because approval rules explicitly blocked it""" + + rules: list[Any] + """Rules that denied the request""" @staticmethod - def from_dict(obj: Any) -> 'AccountGetQuotaResult': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedByRules': assert isinstance(obj, dict) - quota_snapshots = from_dict(AccountQuotaSnapshot.from_dict, obj.get("quotaSnapshots")) - return AccountGetQuotaResult(quota_snapshots) + kind = PermissionDecisionDeniedByRulesKind(obj.get("kind")) + rules = from_list(lambda x: x, obj.get("rules")) + return PermissionDecisionDeniedByRules(kind, rules) def to_dict(self) -> dict: result: dict = {} - result["quotaSnapshots"] = from_dict(lambda x: to_class(AccountQuotaSnapshot, x), self.quota_snapshots) + result["kind"] = to_enum(PermissionDecisionDeniedByRulesKind, self.kind) + result["rules"] = from_list(lambda x: x, self.rules) return result @dataclass -class SessionFSSetProviderRequest: - conventions: SessionFSSetProviderConventions - """Path conventions used by this filesystem""" +class PermissionDecisionDeniedInteractivelyByUser: + kind: PermissionDecisionDeniedInteractivelyByUserKind + """Denied by the user during an interactive prompt""" - initial_cwd: str - """Initial working directory for sessions""" + feedback: str | None = None + """Optional feedback from the user explaining the denial""" - session_state_path: str - """Path within each session's SessionFs where the runtime stores files for that session""" + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionDeniedInteractivelyByUser': + assert isinstance(obj, dict) + kind = PermissionDecisionDeniedInteractivelyByUserKind(obj.get("kind")) + feedback = from_union([from_str, from_none], obj.get("feedback")) + return PermissionDecisionDeniedInteractivelyByUser(kind, feedback) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionDecisionDeniedInteractivelyByUserKind, self.kind) + if self.feedback is not None: + result["feedback"] = from_union([from_str, from_none], self.feedback) + return result + +@dataclass +class PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser: + kind: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind + """Denied because no approval rule matched and user confirmation was unavailable""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': + def from_dict(obj: Any) -> 'PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser': assert isinstance(obj, dict) - conventions = SessionFSSetProviderConventions(obj.get("conventions")) - initial_cwd = from_str(obj.get("initialCwd")) - session_state_path = from_str(obj.get("sessionStatePath")) - return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) + kind = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind(obj.get("kind")) + return PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser(kind) def to_dict(self) -> dict: result: dict = {} - result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) - result["initialCwd"] = from_str(self.initial_cwd) - result["sessionStatePath"] = from_str(self.session_state_path) + result["kind"] = to_enum(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUserKind, self.kind) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class ModeSetRequest: - mode: SessionMode - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" +class PluginList: + plugins: list[Plugin] + """Installed plugins""" @staticmethod - def from_dict(obj: Any) -> 'ModeSetRequest': + def from_dict(obj: Any) -> 'PluginList': assert isinstance(obj, dict) - mode = SessionMode(obj.get("mode")) - return ModeSetRequest(mode) + plugins = from_list(Plugin.from_dict, obj.get("plugins")) + return PluginList(plugins) def to_dict(self) -> dict: result: dict = {} - result["mode"] = to_enum(SessionMode, self.mode) + result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) return result @dataclass -class Workspace: - id: UUID - branch: str | None = None - chronicle_sync_dismissed: bool | None = None - created_at: datetime | None = None - cwd: str | None = None - git_root: str | None = None - host_type: HostType | None = None - mc_last_event_id: str | None = None - mc_session_id: str | None = None - mc_task_id: str | None = None - name: str | None = None - remote_steerable: bool | None = None - repository: str | None = None - session_sync_level: SessionSyncLevel | None = None - summary: str | None = None - summary_count: int | None = None - updated_at: datetime | None = None +class ServerSkillList: + skills: list[ServerSkill] + """All discovered skills across all sources""" @staticmethod - def from_dict(obj: Any) -> 'Workspace': + def from_dict(obj: Any) -> 'ServerSkillList': assert isinstance(obj, dict) - id = UUID(obj.get("id")) - branch = from_union([from_str, from_none], obj.get("branch")) - chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) - created_at = from_union([from_datetime, from_none], obj.get("created_at")) - cwd = from_union([from_str, from_none], obj.get("cwd")) - git_root = from_union([from_str, from_none], obj.get("git_root")) - host_type = from_union([HostType, from_none], obj.get("host_type")) - mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) - mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) - mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) - name = from_union([from_str, from_none], obj.get("name")) - remote_steerable = from_union([from_bool, from_none], obj.get("remote_steerable")) - repository = from_union([from_str, from_none], obj.get("repository")) - session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) - summary = from_union([from_str, from_none], obj.get("summary")) - summary_count = from_union([from_int, from_none], obj.get("summary_count")) - updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) - return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at) + skills = from_list(ServerSkill.from_dict, obj.get("skills")) + return ServerSkillList(skills) def to_dict(self) -> dict: result: dict = {} - result["id"] = str(self.id) - if self.branch is not None: - result["branch"] = from_union([from_str, from_none], self.branch) - if self.chronicle_sync_dismissed is not None: - result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) - if self.created_at is not None: - result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) - if self.cwd is not None: - result["cwd"] = from_union([from_str, from_none], self.cwd) - if self.git_root is not None: - result["git_root"] = from_union([from_str, from_none], self.git_root) - if self.host_type is not None: - result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) - if self.mc_last_event_id is not None: - result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) - if self.mc_session_id is not None: - result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) - if self.mc_task_id is not None: - result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) - if self.name is not None: - result["name"] = from_union([from_str, from_none], self.name) - if self.remote_steerable is not None: - result["remote_steerable"] = from_union([from_bool, from_none], self.remote_steerable) - if self.repository is not None: - result["repository"] = from_union([from_str, from_none], self.repository) - if self.session_sync_level is not None: - result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) - if self.summary is not None: - result["summary"] = from_union([from_str, from_none], self.summary) - if self.summary_count is not None: - result["summary_count"] = from_union([from_int, from_none], self.summary_count) - if self.updated_at is not None: - result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + result["skills"] = from_list(lambda x: to_class(ServerSkill, x), self.skills) return result @dataclass -class InstructionsSources: - content: str - """Raw content of the instruction file""" +class SessionFSError: + """Describes a filesystem error.""" - id: str - """Unique identifier for this source (used for toggling)""" + code: SessionFSErrorCode + """Error classification""" - label: str - """Human-readable label""" + message: str | None = None + """Free-form detail about the error, for logging/diagnostics""" - location: InstructionsSourcesLocation - """Where this source lives — used for UI grouping""" + @staticmethod + def from_dict(obj: Any) -> 'SessionFSError': + assert isinstance(obj, dict) + code = SessionFSErrorCode(obj.get("code")) + message = from_union([from_str, from_none], obj.get("message")) + return SessionFSError(code, message) - source_path: str - """File path relative to repo or absolute for home""" + def to_dict(self) -> dict: + result: dict = {} + result["code"] = to_enum(SessionFSErrorCode, self.code) + if self.message is not None: + result["message"] = from_union([from_str, from_none], self.message) + return result - type: InstructionsSourcesType - """Category of instruction source — used for merge logic""" +@dataclass +class SessionFSReaddirWithTypesEntry: + name: str + """Entry name""" - apply_to: str | None = None - """Glob pattern from frontmatter — when set, this instruction applies only to matching files""" + type: SessionFSReaddirWithTypesEntryType + """Entry type""" - description: str | None = None - """Short description (body after frontmatter) for use in instruction tables""" + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + type = SessionFSReaddirWithTypesEntryType(obj.get("type")) + return SessionFSReaddirWithTypesEntry(name, type) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) + return result + +@dataclass +class SessionFSSetProviderRequest: + conventions: SessionFSSetProviderConventions + """Path conventions used by this filesystem""" + + initial_cwd: str + """Initial working directory for sessions""" + + session_state_path: str + """Path within each session's SessionFs where the runtime stores files for that session""" @staticmethod - def from_dict(obj: Any) -> 'InstructionsSources': + def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': assert isinstance(obj, dict) - content = from_str(obj.get("content")) - id = from_str(obj.get("id")) - label = from_str(obj.get("label")) - location = InstructionsSourcesLocation(obj.get("location")) - source_path = from_str(obj.get("sourcePath")) - type = InstructionsSourcesType(obj.get("type")) - apply_to = from_union([from_str, from_none], obj.get("applyTo")) - description = from_union([from_str, from_none], obj.get("description")) - return InstructionsSources(content, id, label, location, source_path, type, apply_to, description) + conventions = SessionFSSetProviderConventions(obj.get("conventions")) + initial_cwd = from_str(obj.get("initialCwd")) + session_state_path = from_str(obj.get("sessionStatePath")) + return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path) def to_dict(self) -> dict: result: dict = {} - result["content"] = from_str(self.content) - result["id"] = from_str(self.id) - result["label"] = from_str(self.label) - result["location"] = to_enum(InstructionsSourcesLocation, self.location) - result["sourcePath"] = from_str(self.source_path) - result["type"] = to_enum(InstructionsSourcesType, self.type) - if self.apply_to is not None: - result["applyTo"] = from_union([from_str, from_none], self.apply_to) - if self.description is not None: - result["description"] = from_union([from_str, from_none], self.description) + result["conventions"] = to_enum(SessionFSSetProviderConventions, self.conventions) + result["initialCwd"] = from_str(self.initial_cwd) + result["sessionStatePath"] = from_str(self.session_state_path) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class AgentSelectResult: - agent: AgentSelectResultAgent - """The newly selected custom agent""" +class ShellKillRequest: + process_id: str + """Process identifier returned by shell.exec""" + + signal: ShellKillSignal | None = None + """Signal to send (default: SIGTERM)""" @staticmethod - def from_dict(obj: Any) -> 'AgentSelectResult': + def from_dict(obj: Any) -> 'ShellKillRequest': assert isinstance(obj, dict) - agent = AgentSelectResultAgent.from_dict(obj.get("agent")) - return AgentSelectResult(agent) + process_id = from_str(obj.get("processId")) + signal = from_union([ShellKillSignal, from_none], obj.get("signal")) + return ShellKillRequest(process_id, signal) def to_dict(self) -> dict: result: dict = {} - result["agent"] = to_class(AgentSelectResultAgent, self.agent) + result["processId"] = from_str(self.process_id) + if self.signal is not None: + result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -2655,148 +2741,294 @@ def to_dict(self) -> dict: result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class PluginList: - plugins: list[Plugin] - """Installed plugins""" +class ToolList: + tools: list[Tool] + """List of available built-in tools with metadata""" @staticmethod - def from_dict(obj: Any) -> 'PluginList': + def from_dict(obj: Any) -> 'ToolList': assert isinstance(obj, dict) - plugins = from_list(Plugin.from_dict, obj.get("plugins")) - return PluginList(plugins) + tools = from_list(Tool.from_dict, obj.get("tools")) + return ToolList(tools) def to_dict(self) -> dict: result: dict = {} - result["plugins"] = from_list(lambda x: to_class(Plugin, x), self.plugins) + result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) return result @dataclass -class Extension: - id: str - """Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper')""" +class ToolsHandlePendingToolCallRequest: + request_id: str + """Request ID of the pending tool call""" - name: str - """Extension name (directory name)""" + error: str | None = None + """Error message if the tool call failed""" - source: ExtensionSource - """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" + result: ToolCallResult | str | None = None + """Tool call result (string or expanded result object)""" - status: ExtensionStatus - """Current status: running, disabled, failed, or starting""" + @staticmethod + def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) + return ToolsHandlePendingToolCallRequest(request_id, error, result) - pid: int | None = None - """Process ID if the extension is running""" + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result is not None: + result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) + return result + +@dataclass +class UIElicitationArrayAnyOfFieldItems: + any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] @staticmethod - def from_dict(obj: Any) -> 'Extension': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfFieldItems': assert isinstance(obj, dict) - id = from_str(obj.get("id")) - name = from_str(obj.get("name")) - source = ExtensionSource(obj.get("source")) - status = ExtensionStatus(obj.get("status")) - pid = from_union([from_int, from_none], obj.get("pid")) - return Extension(id, name, source, status, pid) + any_of = from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, obj.get("anyOf")) + return UIElicitationArrayAnyOfFieldItems(any_of) def to_dict(self) -> dict: result: dict = {} - result["id"] = from_str(self.id) - result["name"] = from_str(self.name) - result["source"] = to_enum(ExtensionSource, self.source) - result["status"] = to_enum(ExtensionStatus, self.status) - if self.pid is not None: - result["pid"] = from_union([from_int, from_none], self.pid) + result["anyOf"] = from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), self.any_of) return result @dataclass -class LogRequest: - message: str - """Human-readable message""" +class UIElicitationArrayEnumFieldItems: + enum: list[str] + type: UIElicitationArrayEnumFieldItemsType - ephemeral: bool | None = None - """When true, the message is transient and not persisted to the session event log on disk""" + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationArrayEnumFieldItems': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + return UIElicitationArrayEnumFieldItems(enum, type) - level: SessionLogLevel | None = None - """Log severity level. Determines how the message is displayed in the timeline. Defaults to - "info". - """ - url: str | None = None - """Optional URL the user can open in their browser for more details""" + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + return result + +@dataclass +class UIElicitationArrayFieldItems: + enum: list[str] | None = None + type: UIElicitationArrayEnumFieldItemsType | None = None + any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] | None = None @staticmethod - def from_dict(obj: Any) -> 'LogRequest': + def from_dict(obj: Any) -> 'UIElicitationArrayFieldItems': assert isinstance(obj, dict) - message = from_str(obj.get("message")) - ephemeral = from_union([from_bool, from_none], obj.get("ephemeral")) - level = from_union([SessionLogLevel, from_none], obj.get("level")) - url = from_union([from_str, from_none], obj.get("url")) - return LogRequest(message, ephemeral, level, url) + enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) + type = from_union([UIElicitationArrayEnumFieldItemsType, from_none], obj.get("type")) + any_of = from_union([lambda x: from_list(UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict, x), from_none], obj.get("anyOf")) + return UIElicitationArrayFieldItems(enum, type, any_of) def to_dict(self) -> dict: result: dict = {} - result["message"] = from_str(self.message) - if self.ephemeral is not None: - result["ephemeral"] = from_union([from_bool, from_none], self.ephemeral) - if self.level is not None: - result["level"] = from_union([lambda x: to_enum(SessionLogLevel, x), from_none], self.level) - if self.url is not None: - result["url"] = from_union([from_str, from_none], self.url) + if self.enum is not None: + result["enum"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(UIElicitationArrayEnumFieldItemsType, x), from_none], self.type) + if self.any_of is not None: + result["anyOf"] = from_union([lambda x: from_list(lambda x: to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, x), x), from_none], self.any_of) return result @dataclass -class ShellKillRequest: - process_id: str - """Process identifier returned by shell.exec""" +class UIElicitationStringEnumField: + enum: list[str] + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + enum_names: list[str] | None = None + title: str | None = None - signal: ShellKillSignal | None = None - """Signal to send (default: SIGTERM)""" + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringEnumField': + assert isinstance(obj, dict) + enum = from_list(from_str, obj.get("enum")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + enum_names = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enumNames")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringEnumField(enum, type, default, description, enum_names, title) + + def to_dict(self) -> dict: + result: dict = {} + result["enum"] = from_list(from_str, self.enum) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.enum_names is not None: + result["enumNames"] = from_union([lambda x: from_list(from_str, x), from_none], self.enum_names) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationSchemaPropertyString: + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + format: UIElicitationSchemaPropertyStringFormat | None = None + max_length: float | None = None + min_length: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'ShellKillRequest': + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyString': assert isinstance(obj, dict) - process_id = from_str(obj.get("processId")) - signal = from_union([ShellKillSignal, from_none], obj.get("signal")) - return ShellKillRequest(process_id, signal) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + format = from_union([UIElicitationSchemaPropertyStringFormat, from_none], obj.get("format")) + max_length = from_union([from_float, from_none], obj.get("maxLength")) + min_length = from_union([from_float, from_none], obj.get("minLength")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyString(type, default, description, format, max_length, min_length, title) def to_dict(self) -> dict: result: dict = {} - result["processId"] = from_str(self.process_id) - if self.signal is not None: - result["signal"] = from_union([lambda x: to_enum(ShellKillSignal, x), from_none], self.signal) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.format is not None: + result["format"] = from_union([lambda x: to_enum(UIElicitationSchemaPropertyStringFormat, x), from_none], self.format) + if self.max_length is not None: + result["maxLength"] = from_union([to_float, from_none], self.max_length) + if self.min_length is not None: + result["minLength"] = from_union([to_float, from_none], self.min_length) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result -# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class HistoryCompactResult: - messages_removed: int - """Number of messages removed during compaction""" +class UIElicitationStringOneOfField: + one_of: list[UIElicitationStringOneOfFieldOneOf] + type: UIElicitationArrayEnumFieldItemsType + default: str | None = None + description: str | None = None + title: str | None = None - success: bool - """Whether compaction completed successfully""" + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationStringOneOfField': + assert isinstance(obj, dict) + one_of = from_list(UIElicitationStringOneOfFieldOneOf.from_dict, obj.get("oneOf")) + type = UIElicitationArrayEnumFieldItemsType(obj.get("type")) + default = from_union([from_str, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationStringOneOfField(one_of, type, default, description, title) - tokens_removed: int - """Number of tokens freed by compaction""" + def to_dict(self) -> dict: + result: dict = {} + result["oneOf"] = from_list(lambda x: to_class(UIElicitationStringOneOfFieldOneOf, x), self.one_of) + result["type"] = to_enum(UIElicitationArrayEnumFieldItemsType, self.type) + if self.default is not None: + result["default"] = from_union([from_str, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + +@dataclass +class UIElicitationResponse: + """The elicitation response (accept with form values, decline, or cancel)""" + + action: UIElicitationResponseAction + """The user's response: accept (submitted), decline (rejected), or cancel (dismissed)""" + + content: dict[str, float | bool | list[str] | str] | None = None + """The form values submitted by the user (present when action is 'accept')""" + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationResponse': + assert isinstance(obj, dict) + action = UIElicitationResponseAction(obj.get("action")) + content = from_union([lambda x: from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], obj.get("content")) + return UIElicitationResponse(action, content) + + def to_dict(self) -> dict: + result: dict = {} + result["action"] = to_enum(UIElicitationResponseAction, self.action) + if self.content is not None: + result["content"] = from_union([lambda x: from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x), from_none], self.content) + return result + +@dataclass +class UIElicitationSchemaPropertyBoolean: + type: UIElicitationSchemaPropertyBooleanType + default: bool | None = None + description: str | None = None + title: str | None = None + + @staticmethod + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyBoolean': + assert isinstance(obj, dict) + type = UIElicitationSchemaPropertyBooleanType(obj.get("type")) + default = from_union([from_bool, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyBoolean(type, default, description, title) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(UIElicitationSchemaPropertyBooleanType, self.type) + if self.default is not None: + result["default"] = from_union([from_bool, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result - context_window: HistoryCompactContextWindow | None = None - """Post-compaction context window usage breakdown""" +@dataclass +class UIElicitationSchemaPropertyNumber: + type: UIElicitationSchemaPropertyNumberType + default: float | None = None + description: str | None = None + maximum: float | None = None + minimum: float | None = None + title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'HistoryCompactResult': + def from_dict(obj: Any) -> 'UIElicitationSchemaPropertyNumber': assert isinstance(obj, dict) - messages_removed = from_int(obj.get("messagesRemoved")) - success = from_bool(obj.get("success")) - tokens_removed = from_int(obj.get("tokensRemoved")) - context_window = from_union([HistoryCompactContextWindow.from_dict, from_none], obj.get("contextWindow")) - return HistoryCompactResult(messages_removed, success, tokens_removed, context_window) + type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + default = from_union([from_float, from_none], obj.get("default")) + description = from_union([from_str, from_none], obj.get("description")) + maximum = from_union([from_float, from_none], obj.get("maximum")) + minimum = from_union([from_float, from_none], obj.get("minimum")) + title = from_union([from_str, from_none], obj.get("title")) + return UIElicitationSchemaPropertyNumber(type, default, description, maximum, minimum, title) def to_dict(self) -> dict: result: dict = {} - result["messagesRemoved"] = from_int(self.messages_removed) - result["success"] = from_bool(self.success) - result["tokensRemoved"] = from_int(self.tokens_removed) - if self.context_window is not None: - result["contextWindow"] = from_union([lambda x: to_class(HistoryCompactContextWindow, x), from_none], self.context_window) + result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + if self.default is not None: + result["default"] = from_union([to_float, from_none], self.default) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.maximum is not None: + result["maximum"] = from_union([to_float, from_none], self.maximum) + if self.minimum is not None: + result["minimum"] = from_union([to_float, from_none], self.minimum) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) return result @dataclass @@ -2821,40 +3053,131 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSReaddirWithTypesEntry: - name: str - """Entry name""" +class Workspace: + id: UUID + branch: str | None = None + chronicle_sync_dismissed: bool | None = None + created_at: datetime | None = None + cwd: str | None = None + git_root: str | None = None + host_type: HostType | None = None + mc_last_event_id: str | None = None + mc_session_id: str | None = None + mc_task_id: str | None = None + name: str | None = None + remote_steerable: bool | None = None + repository: str | None = None + session_sync_level: SessionSyncLevel | None = None + summary: str | None = None + summary_count: int | None = None + updated_at: datetime | None = None - type: SessionFSReaddirWithTypesEntryType - """Entry type""" + @staticmethod + def from_dict(obj: Any) -> 'Workspace': + assert isinstance(obj, dict) + id = UUID(obj.get("id")) + branch = from_union([from_str, from_none], obj.get("branch")) + chronicle_sync_dismissed = from_union([from_bool, from_none], obj.get("chronicle_sync_dismissed")) + created_at = from_union([from_datetime, from_none], obj.get("created_at")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + git_root = from_union([from_str, from_none], obj.get("git_root")) + host_type = from_union([HostType, from_none], obj.get("host_type")) + mc_last_event_id = from_union([from_str, from_none], obj.get("mc_last_event_id")) + mc_session_id = from_union([from_str, from_none], obj.get("mc_session_id")) + mc_task_id = from_union([from_str, from_none], obj.get("mc_task_id")) + name = from_union([from_str, from_none], obj.get("name")) + remote_steerable = from_union([from_bool, from_none], obj.get("remote_steerable")) + repository = from_union([from_str, from_none], obj.get("repository")) + session_sync_level = from_union([SessionSyncLevel, from_none], obj.get("session_sync_level")) + summary = from_union([from_str, from_none], obj.get("summary")) + summary_count = from_union([from_int, from_none], obj.get("summary_count")) + updated_at = from_union([from_datetime, from_none], obj.get("updated_at")) + return Workspace(id, branch, chronicle_sync_dismissed, created_at, cwd, git_root, host_type, mc_last_event_id, mc_session_id, mc_task_id, name, remote_steerable, repository, session_sync_level, summary, summary_count, updated_at) + + def to_dict(self) -> dict: + result: dict = {} + result["id"] = str(self.id) + if self.branch is not None: + result["branch"] = from_union([from_str, from_none], self.branch) + if self.chronicle_sync_dismissed is not None: + result["chronicle_sync_dismissed"] = from_union([from_bool, from_none], self.chronicle_sync_dismissed) + if self.created_at is not None: + result["created_at"] = from_union([lambda x: x.isoformat(), from_none], self.created_at) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.git_root is not None: + result["git_root"] = from_union([from_str, from_none], self.git_root) + if self.host_type is not None: + result["host_type"] = from_union([lambda x: to_enum(HostType, x), from_none], self.host_type) + if self.mc_last_event_id is not None: + result["mc_last_event_id"] = from_union([from_str, from_none], self.mc_last_event_id) + if self.mc_session_id is not None: + result["mc_session_id"] = from_union([from_str, from_none], self.mc_session_id) + if self.mc_task_id is not None: + result["mc_task_id"] = from_union([from_str, from_none], self.mc_task_id) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.remote_steerable is not None: + result["remote_steerable"] = from_union([from_bool, from_none], self.remote_steerable) + if self.repository is not None: + result["repository"] = from_union([from_str, from_none], self.repository) + if self.session_sync_level is not None: + result["session_sync_level"] = from_union([lambda x: to_enum(SessionSyncLevel, x), from_none], self.session_sync_level) + if self.summary is not None: + result["summary"] = from_union([from_str, from_none], self.summary) + if self.summary_count is not None: + result["summary_count"] = from_union([from_int, from_none], self.summary_count) + if self.updated_at is not None: + result["updated_at"] = from_union([lambda x: x.isoformat(), from_none], self.updated_at) + return result + +@dataclass +class MCPDiscoverResult: + servers: list[DiscoveredMCPServer] + """MCP servers discovered from all sources""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesEntry': + def from_dict(obj: Any) -> 'MCPDiscoverResult': assert isinstance(obj, dict) - name = from_str(obj.get("name")) - type = SessionFSReaddirWithTypesEntryType(obj.get("type")) - return SessionFSReaddirWithTypesEntry(name, type) + servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) + return MCPDiscoverResult(servers) def to_dict(self) -> dict: result: dict = {} - result["name"] = from_str(self.name) - result["type"] = to_enum(SessionFSReaddirWithTypesEntryType, self.type) + result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPConfigList: - servers: dict[str, MCPServerConfig] - """All MCP servers from user config, keyed by name""" +class ExtensionList: + extensions: list[Extension] + """Discovered extensions and their current status""" @staticmethod - def from_dict(obj: Any) -> 'MCPConfigList': + def from_dict(obj: Any) -> 'ExtensionList': assert isinstance(obj, dict) - servers = from_dict(MCPServerConfig.from_dict, obj.get("servers")) - return MCPConfigList(servers) + extensions = from_list(Extension.from_dict, obj.get("extensions")) + return ExtensionList(extensions) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.servers) + result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) + return result + +@dataclass +class InstructionsGetSourcesResult: + sources: list[InstructionsSources] + """Instruction sources for the session""" + + @staticmethod + def from_dict(obj: Any) -> 'InstructionsGetSourcesResult': + assert isinstance(obj, dict) + sources = from_list(InstructionsSources.from_dict, obj.get("sources")) + return InstructionsGetSourcesResult(sources) + + def to_dict(self) -> dict: + result: dict = {} + result["sources"] = from_list(lambda x: to_class(InstructionsSources, x), self.sources) return result @dataclass @@ -2878,6 +3201,22 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result +@dataclass +class MCPConfigList: + servers: dict[str, MCPServerConfig] + """All MCP servers from user config, keyed by name""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPConfigList': + assert isinstance(obj, dict) + servers = from_dict(MCPServerConfig.from_dict, obj.get("servers")) + return MCPConfigList(servers) + + def to_dict(self) -> dict: + result: dict = {} + result["servers"] = from_dict(lambda x: to_class(MCPServerConfig, x), self.servers) + return result + @dataclass class MCPConfigUpdateRequest: config: MCPServerConfig @@ -2900,19 +3239,19 @@ def to_dict(self) -> dict: return result @dataclass -class MCPDiscoverResult: - servers: list[DiscoveredMCPServer] - """MCP servers discovered from all sources""" +class MCPServerList: + servers: list[MCPServer] + """Configured MCP servers""" @staticmethod - def from_dict(obj: Any) -> 'MCPDiscoverResult': + def from_dict(obj: Any) -> 'MCPServerList': assert isinstance(obj, dict) - servers = from_list(DiscoveredMCPServer.from_dict, obj.get("servers")) - return MCPDiscoverResult(servers) + servers = from_list(MCPServer.from_dict, obj.get("servers")) + return MCPServerList(servers) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(DiscoveredMCPServer, x), self.servers) + result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) return result @dataclass @@ -2934,57 +3273,144 @@ def from_dict(obj: Any) -> 'ModelCapabilitiesOverride': def to_dict(self) -> dict: result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimits, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + if self.limits is not None: + result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideLimits, x), from_none], self.limits) + if self.supports is not None: + result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + return result + +@dataclass +class PermissionDecisionRequest: + request_id: str + """Request ID of the pending permission request""" + + result: PermissionDecision + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = PermissionDecision.from_dict(obj.get("result")) + return PermissionDecisionRequest(request_id, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(PermissionDecision, self.result) + return result + +@dataclass +class SessionFSReadFileResult: + content: str + """File content as UTF-8 string""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReadFileResult': + assert isinstance(obj, dict) + content = from_str(obj.get("content")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReadFileResult(content, error) + + def to_dict(self) -> dict: + result: dict = {} + result["content"] = from_str(self.content) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + return result + +@dataclass +class SessionFSReaddirResult: + entries: list[str] + """Entry names in the directory""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSReaddirResult': + assert isinstance(obj, dict) + entries = from_list(from_str, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirResult(entries, error) + + def to_dict(self) -> dict: + result: dict = {} + result["entries"] = from_list(from_str, self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class ModelCapabilitiesClass: - """Override individual model capabilities resolved by the runtime""" +class SessionFSStatResult: + birthtime: datetime + """ISO 8601 timestamp of creation""" - limits: ModelCapabilitiesLimitsClass | None = None - """Token limits for prompts, outputs, and context window""" + is_directory: bool + """Whether the path is a directory""" - supports: ModelCapabilitiesOverrideSupports | None = None - """Feature flags indicating what the model supports""" + is_file: bool + """Whether the path is a file""" + + mtime: datetime + """ISO 8601 timestamp of last modification""" + + size: int + """File size in bytes""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'ModelCapabilitiesClass': + def from_dict(obj: Any) -> 'SessionFSStatResult': assert isinstance(obj, dict) - limits = from_union([ModelCapabilitiesLimitsClass.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesOverrideSupports.from_dict, from_none], obj.get("supports")) - return ModelCapabilitiesClass(limits, supports) + birthtime = from_datetime(obj.get("birthtime")) + is_directory = from_bool(obj.get("isDirectory")) + is_file = from_bool(obj.get("isFile")) + mtime = from_datetime(obj.get("mtime")) + size = from_int(obj.get("size")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size, error) def to_dict(self) -> dict: result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsClass, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesOverrideSupports, x), from_none], self.supports) + result["birthtime"] = self.birthtime.isoformat() + result["isDirectory"] = from_bool(self.is_directory) + result["isFile"] = from_bool(self.is_file) + result["mtime"] = self.mtime.isoformat() + result["size"] = from_int(self.size) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class MCPServerList: - servers: list[MCPServer] - """Configured MCP servers""" +class SessionFSReaddirWithTypesResult: + entries: list[SessionFSReaddirWithTypesEntry] + """Directory entries with type information""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" @staticmethod - def from_dict(obj: Any) -> 'MCPServerList': + def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': assert isinstance(obj, dict) - servers = from_list(MCPServer.from_dict, obj.get("servers")) - return MCPServerList(servers) + entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + return SessionFSReaddirWithTypesResult(entries, error) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) + result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result @dataclass -class UIElicitationArrayEnumField: - items: UIElicitationArrayEnumFieldItems - type: UIElicitationArrayEnumFieldType +class UIElicitationArrayAnyOfField: + items: UIElicitationArrayAnyOfFieldItems + type: UIElicitationArrayAnyOfFieldType default: list[str] | None = None description: str | None = None max_items: float | None = None @@ -2992,21 +3418,21 @@ class UIElicitationArrayEnumField: title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayEnumField': + def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfField': assert isinstance(obj, dict) - items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("items")) - type = UIElicitationArrayEnumFieldType(obj.get("type")) + items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayAnyOfFieldType(obj.get("type")) default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) description = from_union([from_str, from_none], obj.get("description")) max_items = from_union([from_float, from_none], obj.get("maxItems")) min_items = from_union([from_float, from_none], obj.get("minItems")) title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationArrayEnumField(items, type, default, description, max_items, min_items, title) + return UIElicitationArrayAnyOfField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["items"] = to_class(UIElicitationArrayEnumFieldItems, self.items) - result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) + result["items"] = to_class(UIElicitationArrayAnyOfFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayAnyOfFieldType, self.type) if self.default is not None: result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) if self.description is not None: @@ -3020,9 +3446,9 @@ def to_dict(self) -> dict: return result @dataclass -class UIElicitationArrayAnyOfField: - items: UIElicitationArrayAnyOfFieldItems - type: UIElicitationArrayEnumFieldType +class UIElicitationArrayEnumField: + items: UIElicitationArrayEnumFieldItems + type: UIElicitationArrayAnyOfFieldType default: list[str] | None = None description: str | None = None max_items: float | None = None @@ -3030,21 +3456,21 @@ class UIElicitationArrayAnyOfField: title: str | None = None @staticmethod - def from_dict(obj: Any) -> 'UIElicitationArrayAnyOfField': + def from_dict(obj: Any) -> 'UIElicitationArrayEnumField': assert isinstance(obj, dict) - items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("items")) - type = UIElicitationArrayEnumFieldType(obj.get("type")) + items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("items")) + type = UIElicitationArrayAnyOfFieldType(obj.get("type")) default = from_union([lambda x: from_list(from_str, x), from_none], obj.get("default")) description = from_union([from_str, from_none], obj.get("description")) max_items = from_union([from_float, from_none], obj.get("maxItems")) min_items = from_union([from_float, from_none], obj.get("minItems")) title = from_union([from_str, from_none], obj.get("title")) - return UIElicitationArrayAnyOfField(items, type, default, description, max_items, min_items, title) + return UIElicitationArrayEnumField(items, type, default, description, max_items, min_items, title) def to_dict(self) -> dict: result: dict = {} - result["items"] = to_class(UIElicitationArrayAnyOfFieldItems, self.items) - result["type"] = to_enum(UIElicitationArrayEnumFieldType, self.type) + result["items"] = to_class(UIElicitationArrayEnumFieldItems, self.items) + result["type"] = to_enum(UIElicitationArrayAnyOfFieldType, self.type) if self.default is not None: result["default"] = from_union([lambda x: from_list(from_str, x), from_none], self.default) if self.description is not None: @@ -3059,7 +3485,7 @@ def to_dict(self) -> dict: @dataclass class UIElicitationSchemaProperty: - type: UIElicitationSchemaPropertyNumberType + type: UIElicitationSchemaPropertyType default: float | bool | list[str] | str | None = None description: str | None = None enum: list[str] | None = None @@ -3078,7 +3504,7 @@ class UIElicitationSchemaProperty: @staticmethod def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': assert isinstance(obj, dict) - type = UIElicitationSchemaPropertyNumberType(obj.get("type")) + type = UIElicitationSchemaPropertyType(obj.get("type")) default = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], obj.get("default")) description = from_union([from_str, from_none], obj.get("description")) enum = from_union([lambda x: from_list(from_str, x), from_none], obj.get("enum")) @@ -3097,7 +3523,7 @@ def from_dict(obj: Any) -> 'UIElicitationSchemaProperty': def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(UIElicitationSchemaPropertyNumberType, self.type) + result["type"] = to_enum(UIElicitationSchemaPropertyType, self.type) if self.default is not None: result["default"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str, from_none], self.default) if self.description is not None: @@ -3138,170 +3564,15 @@ class UIHandlePendingElicitationRequest: @staticmethod def from_dict(obj: Any) -> 'UIHandlePendingElicitationRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = UIElicitationResponse.from_dict(obj.get("result")) - return UIHandlePendingElicitationRequest(request_id, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(UIElicitationResponse, self.result) - return result - -@dataclass -class PermissionDecisionRequest: - request_id: str - """Request ID of the pending permission request""" - - result: PermissionDecision - - @staticmethod - def from_dict(obj: Any) -> 'PermissionDecisionRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - result = PermissionDecision.from_dict(obj.get("result")) - return PermissionDecisionRequest(request_id, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionDecision, self.result) - return result - -@dataclass -class SessionFSReadFileResult: - content: str - """File content as UTF-8 string""" - - error: SessionFSError | None = None - """Describes a filesystem error.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReadFileResult': - assert isinstance(obj, dict) - content = from_str(obj.get("content")) - error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) - return SessionFSReadFileResult(content, error) - - def to_dict(self) -> dict: - result: dict = {} - result["content"] = from_str(self.content) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) - return result - -@dataclass -class SessionFSStatResult: - birthtime: datetime - """ISO 8601 timestamp of creation""" - - is_directory: bool - """Whether the path is a directory""" - - is_file: bool - """Whether the path is a file""" - - mtime: datetime - """ISO 8601 timestamp of last modification""" - - size: int - """File size in bytes""" - - error: SessionFSError | None = None - """Describes a filesystem error.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSStatResult': - assert isinstance(obj, dict) - birthtime = from_datetime(obj.get("birthtime")) - is_directory = from_bool(obj.get("isDirectory")) - is_file = from_bool(obj.get("isFile")) - mtime = from_datetime(obj.get("mtime")) - size = from_int(obj.get("size")) - error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) - return SessionFSStatResult(birthtime, is_directory, is_file, mtime, size, error) - - def to_dict(self) -> dict: - result: dict = {} - result["birthtime"] = self.birthtime.isoformat() - result["isDirectory"] = from_bool(self.is_directory) - result["isFile"] = from_bool(self.is_file) - result["mtime"] = self.mtime.isoformat() - result["size"] = from_int(self.size) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) - return result - -@dataclass -class SessionFSReaddirResult: - entries: list[str] - """Entry names in the directory""" - - error: SessionFSError | None = None - """Describes a filesystem error.""" - - @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirResult': - assert isinstance(obj, dict) - entries = from_list(from_str, obj.get("entries")) - error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) - return SessionFSReaddirResult(entries, error) - - def to_dict(self) -> dict: - result: dict = {} - result["entries"] = from_list(from_str, self.entries) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) - return result - -@dataclass -class WorkspacesGetWorkspaceResult: - workspace: Workspace | None = None - """Current workspace metadata, or null if not available""" - - @staticmethod - def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': - assert isinstance(obj, dict) - workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) - return WorkspacesGetWorkspaceResult(workspace) - - def to_dict(self) -> dict: - result: dict = {} - result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) - return result - -@dataclass -class InstructionsGetSourcesResult: - sources: list[InstructionsSources] - """Instruction sources for the session""" - - @staticmethod - def from_dict(obj: Any) -> 'InstructionsGetSourcesResult': - assert isinstance(obj, dict) - sources = from_list(InstructionsSources.from_dict, obj.get("sources")) - return InstructionsGetSourcesResult(sources) - - def to_dict(self) -> dict: - result: dict = {} - result["sources"] = from_list(lambda x: to_class(InstructionsSources, x), self.sources) - return result - -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class ExtensionList: - extensions: list[Extension] - """Discovered extensions and their current status""" - - @staticmethod - def from_dict(obj: Any) -> 'ExtensionList': - assert isinstance(obj, dict) - extensions = from_list(Extension.from_dict, obj.get("extensions")) - return ExtensionList(extensions) + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + result = UIElicitationResponse.from_dict(obj.get("result")) + return UIHandlePendingElicitationRequest(request_id, result) def to_dict(self) -> dict: result: dict = {} - result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) + result["requestId"] = from_str(self.request_id) + result["result"] = to_class(UIElicitationResponse, self.result) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -3364,25 +3635,19 @@ def to_dict(self) -> dict: return result @dataclass -class SessionFSReaddirWithTypesResult: - entries: list[SessionFSReaddirWithTypesEntry] - """Directory entries with type information""" - - error: SessionFSError | None = None - """Describes a filesystem error.""" +class WorkspacesGetWorkspaceResult: + workspace: Workspace | None = None + """Current workspace metadata, or null if not available""" @staticmethod - def from_dict(obj: Any) -> 'SessionFSReaddirWithTypesResult': + def from_dict(obj: Any) -> 'WorkspacesGetWorkspaceResult': assert isinstance(obj, dict) - entries = from_list(SessionFSReaddirWithTypesEntry.from_dict, obj.get("entries")) - error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) - return SessionFSReaddirWithTypesResult(entries, error) + workspace = from_union([Workspace.from_dict, from_none], obj.get("workspace")) + return WorkspacesGetWorkspaceResult(workspace) def to_dict(self) -> dict: result: dict = {} - result["entries"] = from_list(lambda x: to_class(SessionFSReaddirWithTypesEntry, x), self.entries) - if self.error is not None: - result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result @dataclass @@ -3392,7 +3657,7 @@ class UIElicitationSchema: properties: dict[str, UIElicitationSchemaProperty] """Form field definitions, keyed by field name""" - type: RequestedSchemaType + type: UIElicitationSchemaType """Schema type indicator (always 'object')""" required: list[str] | None = None @@ -3402,14 +3667,14 @@ class UIElicitationSchema: def from_dict(obj: Any) -> 'UIElicitationSchema': assert isinstance(obj, dict) properties = from_dict(UIElicitationSchemaProperty.from_dict, obj.get("properties")) - type = RequestedSchemaType(obj.get("type")) + type = UIElicitationSchemaType(obj.get("type")) required = from_union([lambda x: from_list(from_str, x), from_none], obj.get("required")) return UIElicitationSchema(properties, type, required) def to_dict(self) -> dict: result: dict = {} result["properties"] = from_dict(lambda x: to_class(UIElicitationSchemaProperty, x), self.properties) - result["type"] = to_enum(RequestedSchemaType, self.type) + result["type"] = to_enum(UIElicitationSchemaType, self.type) if self.required is not None: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result @@ -3460,34 +3725,9 @@ def to_dict(self) -> dict: result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) return result -@dataclass -class CapabilitiesClass: - """Model capabilities and limits""" - - limits: CapabilitiesLimits | None = None - """Token limits for prompts, outputs, and context window""" - - supports: ModelCapabilitiesSupports | None = None - """Feature flags indicating what the model supports""" - - @staticmethod - def from_dict(obj: Any) -> 'CapabilitiesClass': - assert isinstance(obj, dict) - limits = from_union([CapabilitiesLimits.from_dict, from_none], obj.get("limits")) - supports = from_union([ModelCapabilitiesSupports.from_dict, from_none], obj.get("supports")) - return CapabilitiesClass(limits, supports) - - def to_dict(self) -> dict: - result: dict = {} - if self.limits is not None: - result["limits"] = from_union([lambda x: to_class(CapabilitiesLimits, x), from_none], self.limits) - if self.supports is not None: - result["supports"] = from_union([lambda x: to_class(ModelCapabilitiesSupports, x), from_none], self.supports) - return result - @dataclass class Model: - capabilities: CapabilitiesClass + capabilities: ModelCapabilities """Model capabilities and limits""" id: str @@ -3511,7 +3751,7 @@ class Model: @staticmethod def from_dict(obj: Any) -> 'Model': assert isinstance(obj, dict) - capabilities = CapabilitiesClass.from_dict(obj.get("capabilities")) + capabilities = ModelCapabilities.from_dict(obj.get("capabilities")) id = from_str(obj.get("id")) name = from_str(obj.get("name")) billing = from_union([ModelBilling.from_dict, from_none], obj.get("billing")) @@ -3522,7 +3762,7 @@ def from_dict(obj: Any) -> 'Model': def to_dict(self) -> dict: result: dict = {} - result["capabilities"] = to_class(CapabilitiesClass, self.capabilities) + result["capabilities"] = to_class(ModelCapabilities, self.capabilities) result["id"] = from_str(self.id) result["name"] = from_str(self.name) if self.billing is not None: @@ -3556,7 +3796,7 @@ class ModelSwitchToRequest: model_id: str """Model identifier to switch to""" - model_capabilities: ModelCapabilitiesClass | None = None + model_capabilities: ModelCapabilitiesOverride | None = None """Override individual model capabilities resolved by the runtime""" reasoning_effort: str | None = None @@ -3566,7 +3806,7 @@ class ModelSwitchToRequest: def from_dict(obj: Any) -> 'ModelSwitchToRequest': assert isinstance(obj, dict) model_id = from_str(obj.get("modelId")) - model_capabilities = from_union([ModelCapabilitiesClass.from_dict, from_none], obj.get("modelCapabilities")) + model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort) @@ -3574,640 +3814,454 @@ def to_dict(self) -> dict: result: dict = {} result["modelId"] = from_str(self.model_id) if self.model_capabilities is not None: - result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesClass, x), from_none], self.model_capabilities) + result["modelCapabilities"] = from_union([lambda x: to_class(ModelCapabilitiesOverride, x), from_none], self.model_capabilities) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) return result -def model_capabilities_from_dict(s: Any) -> ModelCapabilities: - return ModelCapabilities.from_dict(s) - -def model_capabilities_to_dict(x: ModelCapabilities) -> Any: - return to_class(ModelCapabilities, x) - -def model_capabilities_limits_vision_from_dict(s: Any) -> ModelCapabilitiesLimitsVision: - return ModelCapabilitiesLimitsVision.from_dict(s) - -def model_capabilities_limits_vision_to_dict(x: ModelCapabilitiesLimitsVision) -> Any: - return to_class(ModelCapabilitiesLimitsVision, x) - -def mcp_server_config_from_dict(s: Any) -> MCPServerConfig: - return MCPServerConfig.from_dict(s) - -def mcp_server_config_to_dict(x: MCPServerConfig) -> Any: - return to_class(MCPServerConfig, x) - -def filter_mapping_from_dict(s: Any) -> dict[str, FilterMappingString] | FilterMappingString: - return from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], s) - -def filter_mapping_to_dict(x: dict[str, FilterMappingString] | FilterMappingString) -> Any: - return from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x)], x) - -def discovered_mcp_server_from_dict(s: Any) -> DiscoveredMCPServer: - return DiscoveredMCPServer.from_dict(s) - -def discovered_mcp_server_to_dict(x: DiscoveredMCPServer) -> Any: - return to_class(DiscoveredMCPServer, x) - -def server_skill_list_from_dict(s: Any) -> ServerSkillList: - return ServerSkillList.from_dict(s) - -def server_skill_list_to_dict(x: ServerSkillList) -> Any: - return to_class(ServerSkillList, x) - -def server_skill_from_dict(s: Any) -> ServerSkill: - return ServerSkill.from_dict(s) - -def server_skill_to_dict(x: ServerSkill) -> Any: - return to_class(ServerSkill, x) - -def current_model_from_dict(s: Any) -> CurrentModel: - return CurrentModel.from_dict(s) - -def current_model_to_dict(x: CurrentModel) -> Any: - return to_class(CurrentModel, x) - -def model_capabilities_override_from_dict(s: Any) -> ModelCapabilitiesOverride: - return ModelCapabilitiesOverride.from_dict(s) - -def model_capabilities_override_to_dict(x: ModelCapabilitiesOverride) -> Any: - return to_class(ModelCapabilitiesOverride, x) - -def session_mode_from_dict(s: Any) -> SessionMode: - return SessionMode(s) - -def session_mode_to_dict(x: SessionMode) -> Any: - return to_enum(SessionMode, x) - -def agent_info_from_dict(s: Any) -> AgentInfo: - return AgentInfo.from_dict(s) - -def agent_info_to_dict(x: AgentInfo) -> Any: - return to_class(AgentInfo, x) - -def mcp_server_list_from_dict(s: Any) -> MCPServerList: - return MCPServerList.from_dict(s) - -def mcp_server_list_to_dict(x: MCPServerList) -> Any: - return to_class(MCPServerList, x) - -def tool_call_result_from_dict(s: Any) -> ToolCallResult: - return ToolCallResult.from_dict(s) - -def tool_call_result_to_dict(x: ToolCallResult) -> Any: - return to_class(ToolCallResult, x) - -def handle_tool_call_result_from_dict(s: Any) -> HandleToolCallResult: - return HandleToolCallResult.from_dict(s) - -def handle_tool_call_result_to_dict(x: HandleToolCallResult) -> Any: - return to_class(HandleToolCallResult, x) - -def ui_elicitation_string_enum_field_from_dict(s: Any) -> UIElicitationStringEnumField: - return UIElicitationStringEnumField.from_dict(s) - -def ui_elicitation_string_enum_field_to_dict(x: UIElicitationStringEnumField) -> Any: - return to_class(UIElicitationStringEnumField, x) - -def ui_elicitation_string_one_of_field_from_dict(s: Any) -> UIElicitationStringOneOfField: - return UIElicitationStringOneOfField.from_dict(s) - -def ui_elicitation_string_one_of_field_to_dict(x: UIElicitationStringOneOfField) -> Any: - return to_class(UIElicitationStringOneOfField, x) - -def ui_elicitation_array_enum_field_from_dict(s: Any) -> UIElicitationArrayEnumField: - return UIElicitationArrayEnumField.from_dict(s) - -def ui_elicitation_array_enum_field_to_dict(x: UIElicitationArrayEnumField) -> Any: - return to_class(UIElicitationArrayEnumField, x) - -def ui_elicitation_array_any_of_field_from_dict(s: Any) -> UIElicitationArrayAnyOfField: - return UIElicitationArrayAnyOfField.from_dict(s) - -def ui_elicitation_array_any_of_field_to_dict(x: UIElicitationArrayAnyOfField) -> Any: - return to_class(UIElicitationArrayAnyOfField, x) - -def ui_elicitation_response_from_dict(s: Any) -> UIElicitationResponse: - return UIElicitationResponse.from_dict(s) - -def ui_elicitation_response_to_dict(x: UIElicitationResponse) -> Any: - return to_class(UIElicitationResponse, x) - -def ui_elicitation_response_action_from_dict(s: Any) -> UIElicitationResponseAction: - return UIElicitationResponseAction(s) - -def ui_elicitation_response_action_to_dict(x: UIElicitationResponseAction) -> Any: - return to_enum(UIElicitationResponseAction, x) - -def ui_elicitation_response_content_from_dict(s: Any) -> dict[str, float | bool | list[str] | str]: - return from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), s) - -def ui_elicitation_response_content_to_dict(x: dict[str, float | bool | list[str] | str]) -> Any: - return from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), x) - -def ui_elicitation_field_value_from_dict(s: Any) -> float | bool | list[str] | str: - return from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], s) - -def ui_elicitation_field_value_to_dict(x: float | bool | list[str] | str) -> Any: - return from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x) - -def ui_handle_pending_elicitation_request_from_dict(s: Any) -> UIHandlePendingElicitationRequest: - return UIHandlePendingElicitationRequest.from_dict(s) - -def ui_handle_pending_elicitation_request_to_dict(x: UIHandlePendingElicitationRequest) -> Any: - return to_class(UIHandlePendingElicitationRequest, x) - -def ui_elicitation_result_from_dict(s: Any) -> UIElicitationResult: - return UIElicitationResult.from_dict(s) - -def ui_elicitation_result_to_dict(x: UIElicitationResult) -> Any: - return to_class(UIElicitationResult, x) - -def permission_decision_request_from_dict(s: Any) -> PermissionDecisionRequest: - return PermissionDecisionRequest.from_dict(s) - -def permission_decision_request_to_dict(x: PermissionDecisionRequest) -> Any: - return to_class(PermissionDecisionRequest, x) - -def permission_decision_from_dict(s: Any) -> PermissionDecision: - return PermissionDecision.from_dict(s) - -def permission_decision_to_dict(x: PermissionDecision) -> Any: - return to_class(PermissionDecision, x) - -def permission_request_result_from_dict(s: Any) -> PermissionRequestResult: - return PermissionRequestResult.from_dict(s) - -def permission_request_result_to_dict(x: PermissionRequestResult) -> Any: - return to_class(PermissionRequestResult, x) - -def session_log_level_from_dict(s: Any) -> SessionLogLevel: - return SessionLogLevel(s) - -def session_log_level_to_dict(x: SessionLogLevel) -> Any: - return to_enum(SessionLogLevel, x) - -def session_fs_error_from_dict(s: Any) -> SessionFSError: - return SessionFSError.from_dict(s) - -def session_fs_error_to_dict(x: SessionFSError) -> Any: - return to_class(SessionFSError, x) - -def ping_result_from_dict(s: Any) -> PingResult: - return PingResult.from_dict(s) - -def ping_result_to_dict(x: PingResult) -> Any: - return to_class(PingResult, x) - -def ping_request_from_dict(s: Any) -> PingRequest: - return PingRequest.from_dict(s) - -def ping_request_to_dict(x: PingRequest) -> Any: - return to_class(PingRequest, x) - -def model_list_from_dict(s: Any) -> ModelList: - return ModelList.from_dict(s) - -def model_list_to_dict(x: ModelList) -> Any: - return to_class(ModelList, x) - -def tool_list_from_dict(s: Any) -> ToolList: - return ToolList.from_dict(s) - -def tool_list_to_dict(x: ToolList) -> Any: - return to_class(ToolList, x) - -def tools_list_request_from_dict(s: Any) -> ToolsListRequest: - return ToolsListRequest.from_dict(s) - -def tools_list_request_to_dict(x: ToolsListRequest) -> Any: - return to_class(ToolsListRequest, x) - -def account_get_quota_result_from_dict(s: Any) -> AccountGetQuotaResult: - return AccountGetQuotaResult.from_dict(s) - -def account_get_quota_result_to_dict(x: AccountGetQuotaResult) -> Any: - return to_class(AccountGetQuotaResult, x) - -def mcp_config_list_from_dict(s: Any) -> MCPConfigList: - return MCPConfigList.from_dict(s) - -def mcp_config_list_to_dict(x: MCPConfigList) -> Any: - return to_class(MCPConfigList, x) - -def mcp_config_add_request_from_dict(s: Any) -> MCPConfigAddRequest: - return MCPConfigAddRequest.from_dict(s) - -def mcp_config_add_request_to_dict(x: MCPConfigAddRequest) -> Any: - return to_class(MCPConfigAddRequest, x) - -def mcp_config_update_request_from_dict(s: Any) -> MCPConfigUpdateRequest: - return MCPConfigUpdateRequest.from_dict(s) - -def mcp_config_update_request_to_dict(x: MCPConfigUpdateRequest) -> Any: - return to_class(MCPConfigUpdateRequest, x) - -def mcp_config_remove_request_from_dict(s: Any) -> MCPConfigRemoveRequest: - return MCPConfigRemoveRequest.from_dict(s) - -def mcp_config_remove_request_to_dict(x: MCPConfigRemoveRequest) -> Any: - return to_class(MCPConfigRemoveRequest, x) - -def mcp_discover_result_from_dict(s: Any) -> MCPDiscoverResult: - return MCPDiscoverResult.from_dict(s) - -def mcp_discover_result_to_dict(x: MCPDiscoverResult) -> Any: - return to_class(MCPDiscoverResult, x) - -def mcp_discover_request_from_dict(s: Any) -> MCPDiscoverRequest: - return MCPDiscoverRequest.from_dict(s) - -def mcp_discover_request_to_dict(x: MCPDiscoverRequest) -> Any: - return to_class(MCPDiscoverRequest, x) - -def skills_config_set_disabled_skills_request_from_dict(s: Any) -> SkillsConfigSetDisabledSkillsRequest: - return SkillsConfigSetDisabledSkillsRequest.from_dict(s) - -def skills_config_set_disabled_skills_request_to_dict(x: SkillsConfigSetDisabledSkillsRequest) -> Any: - return to_class(SkillsConfigSetDisabledSkillsRequest, x) - -def skills_discover_request_from_dict(s: Any) -> SkillsDiscoverRequest: - return SkillsDiscoverRequest.from_dict(s) - -def skills_discover_request_to_dict(x: SkillsDiscoverRequest) -> Any: - return to_class(SkillsDiscoverRequest, x) - -def session_fs_set_provider_result_from_dict(s: Any) -> SessionFSSetProviderResult: - return SessionFSSetProviderResult.from_dict(s) - -def session_fs_set_provider_result_to_dict(x: SessionFSSetProviderResult) -> Any: - return to_class(SessionFSSetProviderResult, x) - -def session_fs_set_provider_request_from_dict(s: Any) -> SessionFSSetProviderRequest: - return SessionFSSetProviderRequest.from_dict(s) - -def session_fs_set_provider_request_to_dict(x: SessionFSSetProviderRequest) -> Any: - return to_class(SessionFSSetProviderRequest, x) - -def sessions_fork_result_from_dict(s: Any) -> SessionsForkResult: - return SessionsForkResult.from_dict(s) - -def sessions_fork_result_to_dict(x: SessionsForkResult) -> Any: - return to_class(SessionsForkResult, x) - -def sessions_fork_request_from_dict(s: Any) -> SessionsForkRequest: - return SessionsForkRequest.from_dict(s) - -def sessions_fork_request_to_dict(x: SessionsForkRequest) -> Any: - return to_class(SessionsForkRequest, x) - -def model_switch_to_result_from_dict(s: Any) -> ModelSwitchToResult: - return ModelSwitchToResult.from_dict(s) - -def model_switch_to_result_to_dict(x: ModelSwitchToResult) -> Any: - return to_class(ModelSwitchToResult, x) - -def model_switch_to_request_from_dict(s: Any) -> ModelSwitchToRequest: - return ModelSwitchToRequest.from_dict(s) - -def model_switch_to_request_to_dict(x: ModelSwitchToRequest) -> Any: - return to_class(ModelSwitchToRequest, x) - -def mode_set_request_from_dict(s: Any) -> ModeSetRequest: - return ModeSetRequest.from_dict(s) - -def mode_set_request_to_dict(x: ModeSetRequest) -> Any: - return to_class(ModeSetRequest, x) - -def name_get_result_from_dict(s: Any) -> NameGetResult: - return NameGetResult.from_dict(s) - -def name_get_result_to_dict(x: NameGetResult) -> Any: - return to_class(NameGetResult, x) - -def name_set_request_from_dict(s: Any) -> NameSetRequest: - return NameSetRequest.from_dict(s) - -def name_set_request_to_dict(x: NameSetRequest) -> Any: - return to_class(NameSetRequest, x) - -def plan_read_result_from_dict(s: Any) -> PlanReadResult: - return PlanReadResult.from_dict(s) - -def plan_read_result_to_dict(x: PlanReadResult) -> Any: - return to_class(PlanReadResult, x) - -def plan_update_request_from_dict(s: Any) -> PlanUpdateRequest: - return PlanUpdateRequest.from_dict(s) - -def plan_update_request_to_dict(x: PlanUpdateRequest) -> Any: - return to_class(PlanUpdateRequest, x) - -def workspaces_get_workspace_result_from_dict(s: Any) -> WorkspacesGetWorkspaceResult: - return WorkspacesGetWorkspaceResult.from_dict(s) - -def workspaces_get_workspace_result_to_dict(x: WorkspacesGetWorkspaceResult) -> Any: - return to_class(WorkspacesGetWorkspaceResult, x) - -def workspaces_list_files_result_from_dict(s: Any) -> WorkspacesListFilesResult: - return WorkspacesListFilesResult.from_dict(s) - -def workspaces_list_files_result_to_dict(x: WorkspacesListFilesResult) -> Any: - return to_class(WorkspacesListFilesResult, x) - -def workspaces_read_file_result_from_dict(s: Any) -> WorkspacesReadFileResult: - return WorkspacesReadFileResult.from_dict(s) - -def workspaces_read_file_result_to_dict(x: WorkspacesReadFileResult) -> Any: - return to_class(WorkspacesReadFileResult, x) - -def workspaces_read_file_request_from_dict(s: Any) -> WorkspacesReadFileRequest: - return WorkspacesReadFileRequest.from_dict(s) - -def workspaces_read_file_request_to_dict(x: WorkspacesReadFileRequest) -> Any: - return to_class(WorkspacesReadFileRequest, x) - -def workspaces_create_file_request_from_dict(s: Any) -> WorkspacesCreateFileRequest: - return WorkspacesCreateFileRequest.from_dict(s) - -def workspaces_create_file_request_to_dict(x: WorkspacesCreateFileRequest) -> Any: - return to_class(WorkspacesCreateFileRequest, x) - -def instructions_get_sources_result_from_dict(s: Any) -> InstructionsGetSourcesResult: - return InstructionsGetSourcesResult.from_dict(s) - -def instructions_get_sources_result_to_dict(x: InstructionsGetSourcesResult) -> Any: - return to_class(InstructionsGetSourcesResult, x) - -def fleet_start_result_from_dict(s: Any) -> FleetStartResult: - return FleetStartResult.from_dict(s) - -def fleet_start_result_to_dict(x: FleetStartResult) -> Any: - return to_class(FleetStartResult, x) - -def fleet_start_request_from_dict(s: Any) -> FleetStartRequest: - return FleetStartRequest.from_dict(s) - -def fleet_start_request_to_dict(x: FleetStartRequest) -> Any: - return to_class(FleetStartRequest, x) - -def agent_list_from_dict(s: Any) -> AgentList: - return AgentList.from_dict(s) - -def agent_list_to_dict(x: AgentList) -> Any: - return to_class(AgentList, x) - -def agent_get_current_result_from_dict(s: Any) -> AgentGetCurrentResult: - return AgentGetCurrentResult.from_dict(s) - -def agent_get_current_result_to_dict(x: AgentGetCurrentResult) -> Any: - return to_class(AgentGetCurrentResult, x) - -def agent_select_result_from_dict(s: Any) -> AgentSelectResult: - return AgentSelectResult.from_dict(s) - -def agent_select_result_to_dict(x: AgentSelectResult) -> Any: - return to_class(AgentSelectResult, x) - -def agent_select_request_from_dict(s: Any) -> AgentSelectRequest: - return AgentSelectRequest.from_dict(s) - -def agent_select_request_to_dict(x: AgentSelectRequest) -> Any: - return to_class(AgentSelectRequest, x) - -def agent_reload_result_from_dict(s: Any) -> AgentReloadResult: - return AgentReloadResult.from_dict(s) - -def agent_reload_result_to_dict(x: AgentReloadResult) -> Any: - return to_class(AgentReloadResult, x) - -def skill_list_from_dict(s: Any) -> SkillList: - return SkillList.from_dict(s) - -def skill_list_to_dict(x: SkillList) -> Any: - return to_class(SkillList, x) - -def skills_enable_request_from_dict(s: Any) -> SkillsEnableRequest: - return SkillsEnableRequest.from_dict(s) - -def skills_enable_request_to_dict(x: SkillsEnableRequest) -> Any: - return to_class(SkillsEnableRequest, x) - -def skills_disable_request_from_dict(s: Any) -> SkillsDisableRequest: - return SkillsDisableRequest.from_dict(s) - -def skills_disable_request_to_dict(x: SkillsDisableRequest) -> Any: - return to_class(SkillsDisableRequest, x) - -def mcp_enable_request_from_dict(s: Any) -> MCPEnableRequest: - return MCPEnableRequest.from_dict(s) - -def mcp_enable_request_to_dict(x: MCPEnableRequest) -> Any: - return to_class(MCPEnableRequest, x) - -def mcp_disable_request_from_dict(s: Any) -> MCPDisableRequest: - return MCPDisableRequest.from_dict(s) - -def mcp_disable_request_to_dict(x: MCPDisableRequest) -> Any: - return to_class(MCPDisableRequest, x) - -def plugin_list_from_dict(s: Any) -> PluginList: - return PluginList.from_dict(s) - -def plugin_list_to_dict(x: PluginList) -> Any: - return to_class(PluginList, x) - -def extension_list_from_dict(s: Any) -> ExtensionList: - return ExtensionList.from_dict(s) - -def extension_list_to_dict(x: ExtensionList) -> Any: - return to_class(ExtensionList, x) - -def extensions_enable_request_from_dict(s: Any) -> ExtensionsEnableRequest: - return ExtensionsEnableRequest.from_dict(s) - -def extensions_enable_request_to_dict(x: ExtensionsEnableRequest) -> Any: - return to_class(ExtensionsEnableRequest, x) - -def extensions_disable_request_from_dict(s: Any) -> ExtensionsDisableRequest: - return ExtensionsDisableRequest.from_dict(s) - -def extensions_disable_request_to_dict(x: ExtensionsDisableRequest) -> Any: - return to_class(ExtensionsDisableRequest, x) - -def tools_handle_pending_tool_call_request_from_dict(s: Any) -> ToolsHandlePendingToolCallRequest: - return ToolsHandlePendingToolCallRequest.from_dict(s) - -def tools_handle_pending_tool_call_request_to_dict(x: ToolsHandlePendingToolCallRequest) -> Any: - return to_class(ToolsHandlePendingToolCallRequest, x) - -def commands_handle_pending_command_result_from_dict(s: Any) -> CommandsHandlePendingCommandResult: - return CommandsHandlePendingCommandResult.from_dict(s) - -def commands_handle_pending_command_result_to_dict(x: CommandsHandlePendingCommandResult) -> Any: - return to_class(CommandsHandlePendingCommandResult, x) - -def commands_handle_pending_command_request_from_dict(s: Any) -> CommandsHandlePendingCommandRequest: - return CommandsHandlePendingCommandRequest.from_dict(s) - -def commands_handle_pending_command_request_to_dict(x: CommandsHandlePendingCommandRequest) -> Any: - return to_class(CommandsHandlePendingCommandRequest, x) - -def ui_elicitation_request_from_dict(s: Any) -> UIElicitationRequest: - return UIElicitationRequest.from_dict(s) - -def ui_elicitation_request_to_dict(x: UIElicitationRequest) -> Any: - return to_class(UIElicitationRequest, x) - -def log_result_from_dict(s: Any) -> LogResult: - return LogResult.from_dict(s) - -def log_result_to_dict(x: LogResult) -> Any: - return to_class(LogResult, x) - -def log_request_from_dict(s: Any) -> LogRequest: - return LogRequest.from_dict(s) - -def log_request_to_dict(x: LogRequest) -> Any: - return to_class(LogRequest, x) - -def shell_exec_result_from_dict(s: Any) -> ShellExecResult: - return ShellExecResult.from_dict(s) - -def shell_exec_result_to_dict(x: ShellExecResult) -> Any: - return to_class(ShellExecResult, x) - -def shell_exec_request_from_dict(s: Any) -> ShellExecRequest: - return ShellExecRequest.from_dict(s) - -def shell_exec_request_to_dict(x: ShellExecRequest) -> Any: - return to_class(ShellExecRequest, x) - -def shell_kill_result_from_dict(s: Any) -> ShellKillResult: - return ShellKillResult.from_dict(s) - -def shell_kill_result_to_dict(x: ShellKillResult) -> Any: - return to_class(ShellKillResult, x) - -def shell_kill_request_from_dict(s: Any) -> ShellKillRequest: - return ShellKillRequest.from_dict(s) - -def shell_kill_request_to_dict(x: ShellKillRequest) -> Any: - return to_class(ShellKillRequest, x) - -def history_compact_result_from_dict(s: Any) -> HistoryCompactResult: - return HistoryCompactResult.from_dict(s) - -def history_compact_result_to_dict(x: HistoryCompactResult) -> Any: - return to_class(HistoryCompactResult, x) - -def history_truncate_result_from_dict(s: Any) -> HistoryTruncateResult: - return HistoryTruncateResult.from_dict(s) - -def history_truncate_result_to_dict(x: HistoryTruncateResult) -> Any: - return to_class(HistoryTruncateResult, x) - -def history_truncate_request_from_dict(s: Any) -> HistoryTruncateRequest: - return HistoryTruncateRequest.from_dict(s) - -def history_truncate_request_to_dict(x: HistoryTruncateRequest) -> Any: - return to_class(HistoryTruncateRequest, x) - -def usage_get_metrics_result_from_dict(s: Any) -> UsageGetMetricsResult: - return UsageGetMetricsResult.from_dict(s) - -def usage_get_metrics_result_to_dict(x: UsageGetMetricsResult) -> Any: - return to_class(UsageGetMetricsResult, x) - -def session_fs_read_file_result_from_dict(s: Any) -> SessionFSReadFileResult: - return SessionFSReadFileResult.from_dict(s) - -def session_fs_read_file_result_to_dict(x: SessionFSReadFileResult) -> Any: - return to_class(SessionFSReadFileResult, x) - -def session_fs_read_file_request_from_dict(s: Any) -> SessionFSReadFileRequest: - return SessionFSReadFileRequest.from_dict(s) - -def session_fs_read_file_request_to_dict(x: SessionFSReadFileRequest) -> Any: - return to_class(SessionFSReadFileRequest, x) - -def session_fs_write_file_request_from_dict(s: Any) -> SessionFSWriteFileRequest: - return SessionFSWriteFileRequest.from_dict(s) - -def session_fs_write_file_request_to_dict(x: SessionFSWriteFileRequest) -> Any: - return to_class(SessionFSWriteFileRequest, x) - -def session_fs_append_file_request_from_dict(s: Any) -> SessionFSAppendFileRequest: - return SessionFSAppendFileRequest.from_dict(s) - -def session_fs_append_file_request_to_dict(x: SessionFSAppendFileRequest) -> Any: - return to_class(SessionFSAppendFileRequest, x) - -def session_fs_exists_result_from_dict(s: Any) -> SessionFSExistsResult: - return SessionFSExistsResult.from_dict(s) - -def session_fs_exists_result_to_dict(x: SessionFSExistsResult) -> Any: - return to_class(SessionFSExistsResult, x) - -def session_fs_exists_request_from_dict(s: Any) -> SessionFSExistsRequest: - return SessionFSExistsRequest.from_dict(s) - -def session_fs_exists_request_to_dict(x: SessionFSExistsRequest) -> Any: - return to_class(SessionFSExistsRequest, x) - -def session_fs_stat_result_from_dict(s: Any) -> SessionFSStatResult: - return SessionFSStatResult.from_dict(s) - -def session_fs_stat_result_to_dict(x: SessionFSStatResult) -> Any: - return to_class(SessionFSStatResult, x) - -def session_fs_stat_request_from_dict(s: Any) -> SessionFSStatRequest: - return SessionFSStatRequest.from_dict(s) - -def session_fs_stat_request_to_dict(x: SessionFSStatRequest) -> Any: - return to_class(SessionFSStatRequest, x) - -def session_fs_mkdir_request_from_dict(s: Any) -> SessionFSMkdirRequest: - return SessionFSMkdirRequest.from_dict(s) - -def session_fs_mkdir_request_to_dict(x: SessionFSMkdirRequest) -> Any: - return to_class(SessionFSMkdirRequest, x) - -def session_fs_readdir_result_from_dict(s: Any) -> SessionFSReaddirResult: - return SessionFSReaddirResult.from_dict(s) - -def session_fs_readdir_result_to_dict(x: SessionFSReaddirResult) -> Any: - return to_class(SessionFSReaddirResult, x) - -def session_fs_readdir_request_from_dict(s: Any) -> SessionFSReaddirRequest: - return SessionFSReaddirRequest.from_dict(s) - -def session_fs_readdir_request_to_dict(x: SessionFSReaddirRequest) -> Any: - return to_class(SessionFSReaddirRequest, x) - -def session_fs_readdir_with_types_result_from_dict(s: Any) -> SessionFSReaddirWithTypesResult: - return SessionFSReaddirWithTypesResult.from_dict(s) - -def session_fs_readdir_with_types_result_to_dict(x: SessionFSReaddirWithTypesResult) -> Any: - return to_class(SessionFSReaddirWithTypesResult, x) - -def session_fs_readdir_with_types_request_from_dict(s: Any) -> SessionFSReaddirWithTypesRequest: - return SessionFSReaddirWithTypesRequest.from_dict(s) - -def session_fs_readdir_with_types_request_to_dict(x: SessionFSReaddirWithTypesRequest) -> Any: - return to_class(SessionFSReaddirWithTypesRequest, x) - -def session_fs_rm_request_from_dict(s: Any) -> SessionFSRmRequest: - return SessionFSRmRequest.from_dict(s) - -def session_fs_rm_request_to_dict(x: SessionFSRmRequest) -> Any: - return to_class(SessionFSRmRequest, x) - -def session_fs_rename_request_from_dict(s: Any) -> SessionFSRenameRequest: - return SessionFSRenameRequest.from_dict(s) - -def session_fs_rename_request_to_dict(x: SessionFSRenameRequest) -> Any: - return to_class(SessionFSRenameRequest, x) +@dataclass +class RPC: + account_get_quota_result: AccountGetQuotaResult + account_quota_snapshot: AccountQuotaSnapshot + agent_get_current_result: AgentGetCurrentResult + agent_info: AgentInfo + agent_list: AgentList + agent_reload_result: AgentReloadResult + agent_select_request: AgentSelectRequest + agent_select_result: AgentSelectResult + commands_handle_pending_command_request: CommandsHandlePendingCommandRequest + commands_handle_pending_command_result: CommandsHandlePendingCommandResult + current_model: CurrentModel + discovered_mcp_server: DiscoveredMCPServer + extension: Extension + extension_list: ExtensionList + extensions_disable_request: ExtensionsDisableRequest + extensions_enable_request: ExtensionsEnableRequest + filter_mapping: dict[str, FilterMappingString] | FilterMappingString + fleet_start_request: FleetStartRequest + fleet_start_result: FleetStartResult + handle_tool_call_result: HandleToolCallResult + history_compact_context_window: HistoryCompactContextWindow + history_compact_result: HistoryCompactResult + history_truncate_request: HistoryTruncateRequest + history_truncate_result: HistoryTruncateResult + instructions_get_sources_result: InstructionsGetSourcesResult + instructions_sources: InstructionsSources + log_request: LogRequest + log_result: LogResult + mcp_config_add_request: MCPConfigAddRequest + mcp_config_list: MCPConfigList + mcp_config_remove_request: MCPConfigRemoveRequest + mcp_config_update_request: MCPConfigUpdateRequest + mcp_disable_request: MCPDisableRequest + mcp_discover_request: MCPDiscoverRequest + mcp_discover_result: MCPDiscoverResult + mcp_enable_request: MCPEnableRequest + mcp_server: MCPServer + mcp_server_config: MCPServerConfig + mcp_server_config_http: MCPServerConfigHTTP + mcp_server_config_local: MCPServerConfigLocal + mcp_server_list: MCPServerList + model: Model + model_billing: ModelBilling + model_capabilities: ModelCapabilities + model_capabilities_limits: ModelCapabilitiesLimits + model_capabilities_limits_vision: ModelCapabilitiesLimitsVision + model_capabilities_override: ModelCapabilitiesOverride + model_capabilities_override_limits: ModelCapabilitiesOverrideLimits + model_capabilities_override_limits_vision: ModelCapabilitiesOverrideLimitsVision + model_capabilities_override_supports: ModelCapabilitiesOverrideSupports + model_capabilities_supports: ModelCapabilitiesSupports + model_list: ModelList + model_policy: ModelPolicy + model_switch_to_request: ModelSwitchToRequest + model_switch_to_result: ModelSwitchToResult + mode_set_request: ModeSetRequest + name_get_result: NameGetResult + name_set_request: NameSetRequest + permission_decision: PermissionDecision + permission_decision_approved: PermissionDecisionApproved + permission_decision_denied_by_content_exclusion_policy: PermissionDecisionDeniedByContentExclusionPolicy + permission_decision_denied_by_permission_request_hook: PermissionDecisionDeniedByPermissionRequestHook + permission_decision_denied_by_rules: PermissionDecisionDeniedByRules + permission_decision_denied_interactively_by_user: PermissionDecisionDeniedInteractivelyByUser + permission_decision_denied_no_approval_rule_and_could_not_request_from_user: PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser + permission_decision_request: PermissionDecisionRequest + permission_request_result: PermissionRequestResult + ping_request: PingRequest + ping_result: PingResult + plan_read_result: PlanReadResult + plan_update_request: PlanUpdateRequest + plugin: Plugin + plugin_list: PluginList + server_skill: ServerSkill + server_skill_list: ServerSkillList + session_fs_append_file_request: SessionFSAppendFileRequest + session_fs_error: SessionFSError + session_fs_exists_request: SessionFSExistsRequest + session_fs_exists_result: SessionFSExistsResult + session_fs_mkdir_request: SessionFSMkdirRequest + session_fs_readdir_request: SessionFSReaddirRequest + session_fs_readdir_result: SessionFSReaddirResult + session_fs_readdir_with_types_entry: SessionFSReaddirWithTypesEntry + session_fs_readdir_with_types_request: SessionFSReaddirWithTypesRequest + session_fs_readdir_with_types_result: SessionFSReaddirWithTypesResult + session_fs_read_file_request: SessionFSReadFileRequest + session_fs_read_file_result: SessionFSReadFileResult + session_fs_rename_request: SessionFSRenameRequest + session_fs_rm_request: SessionFSRmRequest + session_fs_set_provider_request: SessionFSSetProviderRequest + session_fs_set_provider_result: SessionFSSetProviderResult + session_fs_stat_request: SessionFSStatRequest + session_fs_stat_result: SessionFSStatResult + session_fs_write_file_request: SessionFSWriteFileRequest + session_log_level: SessionLogLevel + session_mode: SessionMode + sessions_fork_request: SessionsForkRequest + sessions_fork_result: SessionsForkResult + shell_exec_request: ShellExecRequest + shell_exec_result: ShellExecResult + shell_kill_request: ShellKillRequest + shell_kill_result: ShellKillResult + skill: Skill + skill_list: SkillList + skills_config_set_disabled_skills_request: SkillsConfigSetDisabledSkillsRequest + skills_disable_request: SkillsDisableRequest + skills_discover_request: SkillsDiscoverRequest + skills_enable_request: SkillsEnableRequest + tool: Tool + tool_call_result: ToolCallResult + tool_list: ToolList + tools_handle_pending_tool_call_request: ToolsHandlePendingToolCallRequest + tools_list_request: ToolsListRequest + ui_elicitation_array_any_of_field: UIElicitationArrayAnyOfField + ui_elicitation_array_any_of_field_items: UIElicitationArrayAnyOfFieldItems + ui_elicitation_array_any_of_field_items_any_of: UIElicitationArrayAnyOfFieldItemsAnyOf + ui_elicitation_array_enum_field: UIElicitationArrayEnumField + ui_elicitation_array_enum_field_items: UIElicitationArrayEnumFieldItems + ui_elicitation_field_value: float | bool | list[str] | str + ui_elicitation_request: UIElicitationRequest + ui_elicitation_response: UIElicitationResponse + ui_elicitation_response_action: UIElicitationResponseAction + ui_elicitation_response_content: dict[str, float | bool | list[str] | str] + ui_elicitation_result: UIElicitationResult + ui_elicitation_schema: UIElicitationSchema + ui_elicitation_schema_property_boolean: UIElicitationSchemaPropertyBoolean + ui_elicitation_schema_property_number: UIElicitationSchemaPropertyNumber + ui_elicitation_schema_property_string: UIElicitationSchemaPropertyString + ui_elicitation_string_enum_field: UIElicitationStringEnumField + ui_elicitation_string_one_of_field: UIElicitationStringOneOfField + ui_elicitation_string_one_of_field_one_of: UIElicitationStringOneOfFieldOneOf + ui_handle_pending_elicitation_request: UIHandlePendingElicitationRequest + usage_get_metrics_result: UsageGetMetricsResult + usage_metrics_code_changes: UsageMetricsCodeChanges + usage_metrics_model_metric: UsageMetricsModelMetric + usage_metrics_model_metric_requests: UsageMetricsModelMetricRequests + usage_metrics_model_metric_usage: UsageMetricsModelMetricUsage + workspaces_create_file_request: WorkspacesCreateFileRequest + workspaces_get_workspace_result: WorkspacesGetWorkspaceResult + workspaces_list_files_result: WorkspacesListFilesResult + workspaces_read_file_request: WorkspacesReadFileRequest + workspaces_read_file_result: WorkspacesReadFileResult + + @staticmethod + def from_dict(obj: Any) -> 'RPC': + assert isinstance(obj, dict) + account_get_quota_result = AccountGetQuotaResult.from_dict(obj.get("AccountGetQuotaResult")) + account_quota_snapshot = AccountQuotaSnapshot.from_dict(obj.get("AccountQuotaSnapshot")) + agent_get_current_result = AgentGetCurrentResult.from_dict(obj.get("AgentGetCurrentResult")) + agent_info = AgentInfo.from_dict(obj.get("AgentInfo")) + agent_list = AgentList.from_dict(obj.get("AgentList")) + agent_reload_result = AgentReloadResult.from_dict(obj.get("AgentReloadResult")) + agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest")) + agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult")) + commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest")) + commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult")) + current_model = CurrentModel.from_dict(obj.get("CurrentModel")) + discovered_mcp_server = DiscoveredMCPServer.from_dict(obj.get("DiscoveredMcpServer")) + extension = Extension.from_dict(obj.get("Extension")) + extension_list = ExtensionList.from_dict(obj.get("ExtensionList")) + extensions_disable_request = ExtensionsDisableRequest.from_dict(obj.get("ExtensionsDisableRequest")) + extensions_enable_request = ExtensionsEnableRequest.from_dict(obj.get("ExtensionsEnableRequest")) + filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], obj.get("FilterMapping")) + fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) + fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) + handle_tool_call_result = HandleToolCallResult.from_dict(obj.get("HandleToolCallResult")) + history_compact_context_window = HistoryCompactContextWindow.from_dict(obj.get("HistoryCompactContextWindow")) + history_compact_result = HistoryCompactResult.from_dict(obj.get("HistoryCompactResult")) + history_truncate_request = HistoryTruncateRequest.from_dict(obj.get("HistoryTruncateRequest")) + history_truncate_result = HistoryTruncateResult.from_dict(obj.get("HistoryTruncateResult")) + instructions_get_sources_result = InstructionsGetSourcesResult.from_dict(obj.get("InstructionsGetSourcesResult")) + instructions_sources = InstructionsSources.from_dict(obj.get("InstructionsSources")) + log_request = LogRequest.from_dict(obj.get("LogRequest")) + log_result = LogResult.from_dict(obj.get("LogResult")) + mcp_config_add_request = MCPConfigAddRequest.from_dict(obj.get("McpConfigAddRequest")) + mcp_config_list = MCPConfigList.from_dict(obj.get("McpConfigList")) + mcp_config_remove_request = MCPConfigRemoveRequest.from_dict(obj.get("McpConfigRemoveRequest")) + mcp_config_update_request = MCPConfigUpdateRequest.from_dict(obj.get("McpConfigUpdateRequest")) + mcp_disable_request = MCPDisableRequest.from_dict(obj.get("McpDisableRequest")) + mcp_discover_request = MCPDiscoverRequest.from_dict(obj.get("McpDiscoverRequest")) + mcp_discover_result = MCPDiscoverResult.from_dict(obj.get("McpDiscoverResult")) + mcp_enable_request = MCPEnableRequest.from_dict(obj.get("McpEnableRequest")) + mcp_server = MCPServer.from_dict(obj.get("McpServer")) + mcp_server_config = MCPServerConfig.from_dict(obj.get("McpServerConfig")) + mcp_server_config_http = MCPServerConfigHTTP.from_dict(obj.get("McpServerConfigHttp")) + mcp_server_config_local = MCPServerConfigLocal.from_dict(obj.get("McpServerConfigLocal")) + mcp_server_list = MCPServerList.from_dict(obj.get("McpServerList")) + model = Model.from_dict(obj.get("Model")) + model_billing = ModelBilling.from_dict(obj.get("ModelBilling")) + model_capabilities = ModelCapabilities.from_dict(obj.get("ModelCapabilities")) + model_capabilities_limits = ModelCapabilitiesLimits.from_dict(obj.get("ModelCapabilitiesLimits")) + model_capabilities_limits_vision = ModelCapabilitiesLimitsVision.from_dict(obj.get("ModelCapabilitiesLimitsVision")) + model_capabilities_override = ModelCapabilitiesOverride.from_dict(obj.get("ModelCapabilitiesOverride")) + model_capabilities_override_limits = ModelCapabilitiesOverrideLimits.from_dict(obj.get("ModelCapabilitiesOverrideLimits")) + model_capabilities_override_limits_vision = ModelCapabilitiesOverrideLimitsVision.from_dict(obj.get("ModelCapabilitiesOverrideLimitsVision")) + model_capabilities_override_supports = ModelCapabilitiesOverrideSupports.from_dict(obj.get("ModelCapabilitiesOverrideSupports")) + model_capabilities_supports = ModelCapabilitiesSupports.from_dict(obj.get("ModelCapabilitiesSupports")) + model_list = ModelList.from_dict(obj.get("ModelList")) + model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) + model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) + model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) + mode_set_request = ModeSetRequest.from_dict(obj.get("ModeSetRequest")) + name_get_result = NameGetResult.from_dict(obj.get("NameGetResult")) + name_set_request = NameSetRequest.from_dict(obj.get("NameSetRequest")) + permission_decision = PermissionDecision.from_dict(obj.get("PermissionDecision")) + permission_decision_approved = PermissionDecisionApproved.from_dict(obj.get("PermissionDecisionApproved")) + permission_decision_denied_by_content_exclusion_policy = PermissionDecisionDeniedByContentExclusionPolicy.from_dict(obj.get("PermissionDecisionDeniedByContentExclusionPolicy")) + permission_decision_denied_by_permission_request_hook = PermissionDecisionDeniedByPermissionRequestHook.from_dict(obj.get("PermissionDecisionDeniedByPermissionRequestHook")) + permission_decision_denied_by_rules = PermissionDecisionDeniedByRules.from_dict(obj.get("PermissionDecisionDeniedByRules")) + permission_decision_denied_interactively_by_user = PermissionDecisionDeniedInteractivelyByUser.from_dict(obj.get("PermissionDecisionDeniedInteractivelyByUser")) + permission_decision_denied_no_approval_rule_and_could_not_request_from_user = PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser.from_dict(obj.get("PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser")) + permission_decision_request = PermissionDecisionRequest.from_dict(obj.get("PermissionDecisionRequest")) + permission_request_result = PermissionRequestResult.from_dict(obj.get("PermissionRequestResult")) + ping_request = PingRequest.from_dict(obj.get("PingRequest")) + ping_result = PingResult.from_dict(obj.get("PingResult")) + plan_read_result = PlanReadResult.from_dict(obj.get("PlanReadResult")) + plan_update_request = PlanUpdateRequest.from_dict(obj.get("PlanUpdateRequest")) + plugin = Plugin.from_dict(obj.get("Plugin")) + plugin_list = PluginList.from_dict(obj.get("PluginList")) + server_skill = ServerSkill.from_dict(obj.get("ServerSkill")) + server_skill_list = ServerSkillList.from_dict(obj.get("ServerSkillList")) + session_fs_append_file_request = SessionFSAppendFileRequest.from_dict(obj.get("SessionFsAppendFileRequest")) + session_fs_error = SessionFSError.from_dict(obj.get("SessionFsError")) + session_fs_exists_request = SessionFSExistsRequest.from_dict(obj.get("SessionFsExistsRequest")) + session_fs_exists_result = SessionFSExistsResult.from_dict(obj.get("SessionFsExistsResult")) + session_fs_mkdir_request = SessionFSMkdirRequest.from_dict(obj.get("SessionFsMkdirRequest")) + session_fs_readdir_request = SessionFSReaddirRequest.from_dict(obj.get("SessionFsReaddirRequest")) + session_fs_readdir_result = SessionFSReaddirResult.from_dict(obj.get("SessionFsReaddirResult")) + session_fs_readdir_with_types_entry = SessionFSReaddirWithTypesEntry.from_dict(obj.get("SessionFsReaddirWithTypesEntry")) + session_fs_readdir_with_types_request = SessionFSReaddirWithTypesRequest.from_dict(obj.get("SessionFsReaddirWithTypesRequest")) + session_fs_readdir_with_types_result = SessionFSReaddirWithTypesResult.from_dict(obj.get("SessionFsReaddirWithTypesResult")) + session_fs_read_file_request = SessionFSReadFileRequest.from_dict(obj.get("SessionFsReadFileRequest")) + session_fs_read_file_result = SessionFSReadFileResult.from_dict(obj.get("SessionFsReadFileResult")) + session_fs_rename_request = SessionFSRenameRequest.from_dict(obj.get("SessionFsRenameRequest")) + session_fs_rm_request = SessionFSRmRequest.from_dict(obj.get("SessionFsRmRequest")) + session_fs_set_provider_request = SessionFSSetProviderRequest.from_dict(obj.get("SessionFsSetProviderRequest")) + session_fs_set_provider_result = SessionFSSetProviderResult.from_dict(obj.get("SessionFsSetProviderResult")) + session_fs_stat_request = SessionFSStatRequest.from_dict(obj.get("SessionFsStatRequest")) + session_fs_stat_result = SessionFSStatResult.from_dict(obj.get("SessionFsStatResult")) + session_fs_write_file_request = SessionFSWriteFileRequest.from_dict(obj.get("SessionFsWriteFileRequest")) + session_log_level = SessionLogLevel(obj.get("SessionLogLevel")) + session_mode = SessionMode(obj.get("SessionMode")) + sessions_fork_request = SessionsForkRequest.from_dict(obj.get("SessionsForkRequest")) + sessions_fork_result = SessionsForkResult.from_dict(obj.get("SessionsForkResult")) + shell_exec_request = ShellExecRequest.from_dict(obj.get("ShellExecRequest")) + shell_exec_result = ShellExecResult.from_dict(obj.get("ShellExecResult")) + shell_kill_request = ShellKillRequest.from_dict(obj.get("ShellKillRequest")) + shell_kill_result = ShellKillResult.from_dict(obj.get("ShellKillResult")) + skill = Skill.from_dict(obj.get("Skill")) + skill_list = SkillList.from_dict(obj.get("SkillList")) + skills_config_set_disabled_skills_request = SkillsConfigSetDisabledSkillsRequest.from_dict(obj.get("SkillsConfigSetDisabledSkillsRequest")) + skills_disable_request = SkillsDisableRequest.from_dict(obj.get("SkillsDisableRequest")) + skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest")) + skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest")) + tool = Tool.from_dict(obj.get("Tool")) + tool_call_result = ToolCallResult.from_dict(obj.get("ToolCallResult")) + tool_list = ToolList.from_dict(obj.get("ToolList")) + tools_handle_pending_tool_call_request = ToolsHandlePendingToolCallRequest.from_dict(obj.get("ToolsHandlePendingToolCallRequest")) + tools_list_request = ToolsListRequest.from_dict(obj.get("ToolsListRequest")) + ui_elicitation_array_any_of_field = UIElicitationArrayAnyOfField.from_dict(obj.get("UIElicitationArrayAnyOfField")) + ui_elicitation_array_any_of_field_items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("UIElicitationArrayAnyOfFieldItems")) + ui_elicitation_array_any_of_field_items_any_of = UIElicitationArrayAnyOfFieldItemsAnyOf.from_dict(obj.get("UIElicitationArrayAnyOfFieldItemsAnyOf")) + ui_elicitation_array_enum_field = UIElicitationArrayEnumField.from_dict(obj.get("UIElicitationArrayEnumField")) + ui_elicitation_array_enum_field_items = UIElicitationArrayEnumFieldItems.from_dict(obj.get("UIElicitationArrayEnumFieldItems")) + ui_elicitation_field_value = from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], obj.get("UIElicitationFieldValue")) + ui_elicitation_request = UIElicitationRequest.from_dict(obj.get("UIElicitationRequest")) + ui_elicitation_response = UIElicitationResponse.from_dict(obj.get("UIElicitationResponse")) + ui_elicitation_response_action = UIElicitationResponseAction(obj.get("UIElicitationResponseAction")) + ui_elicitation_response_content = from_dict(lambda x: from_union([from_float, from_bool, lambda x: from_list(from_str, x), from_str], x), obj.get("UIElicitationResponseContent")) + ui_elicitation_result = UIElicitationResult.from_dict(obj.get("UIElicitationResult")) + ui_elicitation_schema = UIElicitationSchema.from_dict(obj.get("UIElicitationSchema")) + ui_elicitation_schema_property_boolean = UIElicitationSchemaPropertyBoolean.from_dict(obj.get("UIElicitationSchemaPropertyBoolean")) + ui_elicitation_schema_property_number = UIElicitationSchemaPropertyNumber.from_dict(obj.get("UIElicitationSchemaPropertyNumber")) + ui_elicitation_schema_property_string = UIElicitationSchemaPropertyString.from_dict(obj.get("UIElicitationSchemaPropertyString")) + ui_elicitation_string_enum_field = UIElicitationStringEnumField.from_dict(obj.get("UIElicitationStringEnumField")) + ui_elicitation_string_one_of_field = UIElicitationStringOneOfField.from_dict(obj.get("UIElicitationStringOneOfField")) + ui_elicitation_string_one_of_field_one_of = UIElicitationStringOneOfFieldOneOf.from_dict(obj.get("UIElicitationStringOneOfFieldOneOf")) + ui_handle_pending_elicitation_request = UIHandlePendingElicitationRequest.from_dict(obj.get("UIHandlePendingElicitationRequest")) + usage_get_metrics_result = UsageGetMetricsResult.from_dict(obj.get("UsageGetMetricsResult")) + usage_metrics_code_changes = UsageMetricsCodeChanges.from_dict(obj.get("UsageMetricsCodeChanges")) + usage_metrics_model_metric = UsageMetricsModelMetric.from_dict(obj.get("UsageMetricsModelMetric")) + usage_metrics_model_metric_requests = UsageMetricsModelMetricRequests.from_dict(obj.get("UsageMetricsModelMetricRequests")) + usage_metrics_model_metric_usage = UsageMetricsModelMetricUsage.from_dict(obj.get("UsageMetricsModelMetricUsage")) + workspaces_create_file_request = WorkspacesCreateFileRequest.from_dict(obj.get("WorkspacesCreateFileRequest")) + workspaces_get_workspace_result = WorkspacesGetWorkspaceResult.from_dict(obj.get("WorkspacesGetWorkspaceResult")) + workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult")) + workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest")) + workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult")) + return RPC(account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, extension, extension_list, extensions_disable_request, extensions_enable_request, filter_mapping, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, log_request, log_result, mcp_config_add_request, mcp_config_list, mcp_config_remove_request, mcp_config_update_request, mcp_disable_request, mcp_discover_request, mcp_discover_result, mcp_enable_request, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_local, mcp_server_list, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approved, permission_decision_denied_by_content_exclusion_policy, permission_decision_denied_by_permission_request_hook, permission_decision_denied_by_rules, permission_decision_denied_interactively_by_user, permission_decision_denied_no_approval_rule_and_could_not_request_from_user, permission_decision_request, permission_request_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, server_skill, server_skill_list, session_fs_append_file_request, session_fs_error, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, tool, tool_call_result, tool_list, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_string, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_usage, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) + + def to_dict(self) -> dict: + result: dict = {} + result["AccountGetQuotaResult"] = to_class(AccountGetQuotaResult, self.account_get_quota_result) + result["AccountQuotaSnapshot"] = to_class(AccountQuotaSnapshot, self.account_quota_snapshot) + result["AgentGetCurrentResult"] = to_class(AgentGetCurrentResult, self.agent_get_current_result) + result["AgentInfo"] = to_class(AgentInfo, self.agent_info) + result["AgentList"] = to_class(AgentList, self.agent_list) + result["AgentReloadResult"] = to_class(AgentReloadResult, self.agent_reload_result) + result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request) + result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result) + result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request) + result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result) + result["CurrentModel"] = to_class(CurrentModel, self.current_model) + result["DiscoveredMcpServer"] = to_class(DiscoveredMCPServer, self.discovered_mcp_server) + result["Extension"] = to_class(Extension, self.extension) + result["ExtensionList"] = to_class(ExtensionList, self.extension_list) + result["ExtensionsDisableRequest"] = to_class(ExtensionsDisableRequest, self.extensions_disable_request) + result["ExtensionsEnableRequest"] = to_class(ExtensionsEnableRequest, self.extensions_enable_request) + result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(FilterMappingString, x), x), lambda x: to_enum(FilterMappingString, x)], self.filter_mapping) + result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) + result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) + result["HandleToolCallResult"] = to_class(HandleToolCallResult, self.handle_tool_call_result) + result["HistoryCompactContextWindow"] = to_class(HistoryCompactContextWindow, self.history_compact_context_window) + result["HistoryCompactResult"] = to_class(HistoryCompactResult, self.history_compact_result) + result["HistoryTruncateRequest"] = to_class(HistoryTruncateRequest, self.history_truncate_request) + result["HistoryTruncateResult"] = to_class(HistoryTruncateResult, self.history_truncate_result) + result["InstructionsGetSourcesResult"] = to_class(InstructionsGetSourcesResult, self.instructions_get_sources_result) + result["InstructionsSources"] = to_class(InstructionsSources, self.instructions_sources) + result["LogRequest"] = to_class(LogRequest, self.log_request) + result["LogResult"] = to_class(LogResult, self.log_result) + result["McpConfigAddRequest"] = to_class(MCPConfigAddRequest, self.mcp_config_add_request) + result["McpConfigList"] = to_class(MCPConfigList, self.mcp_config_list) + result["McpConfigRemoveRequest"] = to_class(MCPConfigRemoveRequest, self.mcp_config_remove_request) + result["McpConfigUpdateRequest"] = to_class(MCPConfigUpdateRequest, self.mcp_config_update_request) + result["McpDisableRequest"] = to_class(MCPDisableRequest, self.mcp_disable_request) + result["McpDiscoverRequest"] = to_class(MCPDiscoverRequest, self.mcp_discover_request) + result["McpDiscoverResult"] = to_class(MCPDiscoverResult, self.mcp_discover_result) + result["McpEnableRequest"] = to_class(MCPEnableRequest, self.mcp_enable_request) + result["McpServer"] = to_class(MCPServer, self.mcp_server) + result["McpServerConfig"] = to_class(MCPServerConfig, self.mcp_server_config) + result["McpServerConfigHttp"] = to_class(MCPServerConfigHTTP, self.mcp_server_config_http) + result["McpServerConfigLocal"] = to_class(MCPServerConfigLocal, self.mcp_server_config_local) + result["McpServerList"] = to_class(MCPServerList, self.mcp_server_list) + result["Model"] = to_class(Model, self.model) + result["ModelBilling"] = to_class(ModelBilling, self.model_billing) + result["ModelCapabilities"] = to_class(ModelCapabilities, self.model_capabilities) + result["ModelCapabilitiesLimits"] = to_class(ModelCapabilitiesLimits, self.model_capabilities_limits) + result["ModelCapabilitiesLimitsVision"] = to_class(ModelCapabilitiesLimitsVision, self.model_capabilities_limits_vision) + result["ModelCapabilitiesOverride"] = to_class(ModelCapabilitiesOverride, self.model_capabilities_override) + result["ModelCapabilitiesOverrideLimits"] = to_class(ModelCapabilitiesOverrideLimits, self.model_capabilities_override_limits) + result["ModelCapabilitiesOverrideLimitsVision"] = to_class(ModelCapabilitiesOverrideLimitsVision, self.model_capabilities_override_limits_vision) + result["ModelCapabilitiesOverrideSupports"] = to_class(ModelCapabilitiesOverrideSupports, self.model_capabilities_override_supports) + result["ModelCapabilitiesSupports"] = to_class(ModelCapabilitiesSupports, self.model_capabilities_supports) + result["ModelList"] = to_class(ModelList, self.model_list) + result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) + result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) + result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) + result["ModeSetRequest"] = to_class(ModeSetRequest, self.mode_set_request) + result["NameGetResult"] = to_class(NameGetResult, self.name_get_result) + result["NameSetRequest"] = to_class(NameSetRequest, self.name_set_request) + result["PermissionDecision"] = to_class(PermissionDecision, self.permission_decision) + result["PermissionDecisionApproved"] = to_class(PermissionDecisionApproved, self.permission_decision_approved) + result["PermissionDecisionDeniedByContentExclusionPolicy"] = to_class(PermissionDecisionDeniedByContentExclusionPolicy, self.permission_decision_denied_by_content_exclusion_policy) + result["PermissionDecisionDeniedByPermissionRequestHook"] = to_class(PermissionDecisionDeniedByPermissionRequestHook, self.permission_decision_denied_by_permission_request_hook) + result["PermissionDecisionDeniedByRules"] = to_class(PermissionDecisionDeniedByRules, self.permission_decision_denied_by_rules) + result["PermissionDecisionDeniedInteractivelyByUser"] = to_class(PermissionDecisionDeniedInteractivelyByUser, self.permission_decision_denied_interactively_by_user) + result["PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser"] = to_class(PermissionDecisionDeniedNoApprovalRuleAndCouldNotRequestFromUser, self.permission_decision_denied_no_approval_rule_and_could_not_request_from_user) + result["PermissionDecisionRequest"] = to_class(PermissionDecisionRequest, self.permission_decision_request) + result["PermissionRequestResult"] = to_class(PermissionRequestResult, self.permission_request_result) + result["PingRequest"] = to_class(PingRequest, self.ping_request) + result["PingResult"] = to_class(PingResult, self.ping_result) + result["PlanReadResult"] = to_class(PlanReadResult, self.plan_read_result) + result["PlanUpdateRequest"] = to_class(PlanUpdateRequest, self.plan_update_request) + result["Plugin"] = to_class(Plugin, self.plugin) + result["PluginList"] = to_class(PluginList, self.plugin_list) + result["ServerSkill"] = to_class(ServerSkill, self.server_skill) + result["ServerSkillList"] = to_class(ServerSkillList, self.server_skill_list) + result["SessionFsAppendFileRequest"] = to_class(SessionFSAppendFileRequest, self.session_fs_append_file_request) + result["SessionFsError"] = to_class(SessionFSError, self.session_fs_error) + result["SessionFsExistsRequest"] = to_class(SessionFSExistsRequest, self.session_fs_exists_request) + result["SessionFsExistsResult"] = to_class(SessionFSExistsResult, self.session_fs_exists_result) + result["SessionFsMkdirRequest"] = to_class(SessionFSMkdirRequest, self.session_fs_mkdir_request) + result["SessionFsReaddirRequest"] = to_class(SessionFSReaddirRequest, self.session_fs_readdir_request) + result["SessionFsReaddirResult"] = to_class(SessionFSReaddirResult, self.session_fs_readdir_result) + result["SessionFsReaddirWithTypesEntry"] = to_class(SessionFSReaddirWithTypesEntry, self.session_fs_readdir_with_types_entry) + result["SessionFsReaddirWithTypesRequest"] = to_class(SessionFSReaddirWithTypesRequest, self.session_fs_readdir_with_types_request) + result["SessionFsReaddirWithTypesResult"] = to_class(SessionFSReaddirWithTypesResult, self.session_fs_readdir_with_types_result) + result["SessionFsReadFileRequest"] = to_class(SessionFSReadFileRequest, self.session_fs_read_file_request) + result["SessionFsReadFileResult"] = to_class(SessionFSReadFileResult, self.session_fs_read_file_result) + result["SessionFsRenameRequest"] = to_class(SessionFSRenameRequest, self.session_fs_rename_request) + result["SessionFsRmRequest"] = to_class(SessionFSRmRequest, self.session_fs_rm_request) + result["SessionFsSetProviderRequest"] = to_class(SessionFSSetProviderRequest, self.session_fs_set_provider_request) + result["SessionFsSetProviderResult"] = to_class(SessionFSSetProviderResult, self.session_fs_set_provider_result) + result["SessionFsStatRequest"] = to_class(SessionFSStatRequest, self.session_fs_stat_request) + result["SessionFsStatResult"] = to_class(SessionFSStatResult, self.session_fs_stat_result) + result["SessionFsWriteFileRequest"] = to_class(SessionFSWriteFileRequest, self.session_fs_write_file_request) + result["SessionLogLevel"] = to_enum(SessionLogLevel, self.session_log_level) + result["SessionMode"] = to_enum(SessionMode, self.session_mode) + result["SessionsForkRequest"] = to_class(SessionsForkRequest, self.sessions_fork_request) + result["SessionsForkResult"] = to_class(SessionsForkResult, self.sessions_fork_result) + result["ShellExecRequest"] = to_class(ShellExecRequest, self.shell_exec_request) + result["ShellExecResult"] = to_class(ShellExecResult, self.shell_exec_result) + result["ShellKillRequest"] = to_class(ShellKillRequest, self.shell_kill_request) + result["ShellKillResult"] = to_class(ShellKillResult, self.shell_kill_result) + result["Skill"] = to_class(Skill, self.skill) + result["SkillList"] = to_class(SkillList, self.skill_list) + result["SkillsConfigSetDisabledSkillsRequest"] = to_class(SkillsConfigSetDisabledSkillsRequest, self.skills_config_set_disabled_skills_request) + result["SkillsDisableRequest"] = to_class(SkillsDisableRequest, self.skills_disable_request) + result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request) + result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request) + result["Tool"] = to_class(Tool, self.tool) + result["ToolCallResult"] = to_class(ToolCallResult, self.tool_call_result) + result["ToolList"] = to_class(ToolList, self.tool_list) + result["ToolsHandlePendingToolCallRequest"] = to_class(ToolsHandlePendingToolCallRequest, self.tools_handle_pending_tool_call_request) + result["ToolsListRequest"] = to_class(ToolsListRequest, self.tools_list_request) + result["UIElicitationArrayAnyOfField"] = to_class(UIElicitationArrayAnyOfField, self.ui_elicitation_array_any_of_field) + result["UIElicitationArrayAnyOfFieldItems"] = to_class(UIElicitationArrayAnyOfFieldItems, self.ui_elicitation_array_any_of_field_items) + result["UIElicitationArrayAnyOfFieldItemsAnyOf"] = to_class(UIElicitationArrayAnyOfFieldItemsAnyOf, self.ui_elicitation_array_any_of_field_items_any_of) + result["UIElicitationArrayEnumField"] = to_class(UIElicitationArrayEnumField, self.ui_elicitation_array_enum_field) + result["UIElicitationArrayEnumFieldItems"] = to_class(UIElicitationArrayEnumFieldItems, self.ui_elicitation_array_enum_field_items) + result["UIElicitationFieldValue"] = from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], self.ui_elicitation_field_value) + result["UIElicitationRequest"] = to_class(UIElicitationRequest, self.ui_elicitation_request) + result["UIElicitationResponse"] = to_class(UIElicitationResponse, self.ui_elicitation_response) + result["UIElicitationResponseAction"] = to_enum(UIElicitationResponseAction, self.ui_elicitation_response_action) + result["UIElicitationResponseContent"] = from_dict(lambda x: from_union([to_float, from_bool, lambda x: from_list(from_str, x), from_str], x), self.ui_elicitation_response_content) + result["UIElicitationResult"] = to_class(UIElicitationResult, self.ui_elicitation_result) + result["UIElicitationSchema"] = to_class(UIElicitationSchema, self.ui_elicitation_schema) + result["UIElicitationSchemaPropertyBoolean"] = to_class(UIElicitationSchemaPropertyBoolean, self.ui_elicitation_schema_property_boolean) + result["UIElicitationSchemaPropertyNumber"] = to_class(UIElicitationSchemaPropertyNumber, self.ui_elicitation_schema_property_number) + result["UIElicitationSchemaPropertyString"] = to_class(UIElicitationSchemaPropertyString, self.ui_elicitation_schema_property_string) + result["UIElicitationStringEnumField"] = to_class(UIElicitationStringEnumField, self.ui_elicitation_string_enum_field) + result["UIElicitationStringOneOfField"] = to_class(UIElicitationStringOneOfField, self.ui_elicitation_string_one_of_field) + result["UIElicitationStringOneOfFieldOneOf"] = to_class(UIElicitationStringOneOfFieldOneOf, self.ui_elicitation_string_one_of_field_one_of) + result["UIHandlePendingElicitationRequest"] = to_class(UIHandlePendingElicitationRequest, self.ui_handle_pending_elicitation_request) + result["UsageGetMetricsResult"] = to_class(UsageGetMetricsResult, self.usage_get_metrics_result) + result["UsageMetricsCodeChanges"] = to_class(UsageMetricsCodeChanges, self.usage_metrics_code_changes) + result["UsageMetricsModelMetric"] = to_class(UsageMetricsModelMetric, self.usage_metrics_model_metric) + result["UsageMetricsModelMetricRequests"] = to_class(UsageMetricsModelMetricRequests, self.usage_metrics_model_metric_requests) + result["UsageMetricsModelMetricUsage"] = to_class(UsageMetricsModelMetricUsage, self.usage_metrics_model_metric_usage) + result["WorkspacesCreateFileRequest"] = to_class(WorkspacesCreateFileRequest, self.workspaces_create_file_request) + result["WorkspacesGetWorkspaceResult"] = to_class(WorkspacesGetWorkspaceResult, self.workspaces_get_workspace_result) + result["WorkspacesListFilesResult"] = to_class(WorkspacesListFilesResult, self.workspaces_list_files_result) + result["WorkspacesReadFileRequest"] = to_class(WorkspacesReadFileRequest, self.workspaces_read_file_request) + result["WorkspacesReadFileResult"] = to_class(WorkspacesReadFileResult, self.workspaces_read_file_result) + return result + +def rpc_from_dict(s: Any) -> RPC: + return RPC.from_dict(s) + +def rpc_to_dict(x: RPC) -> Any: + return to_class(RPC, x) def _timeout_kwargs(timeout: float | None) -> dict: diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 5992bf32d..21c48b6d0 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -2117,21 +2117,21 @@ def to_dict(self) -> dict: @dataclass -class ToolExecutionCompleteDataResultContentsItemIconsItem: +class ToolExecutionCompleteContentResourceLinkIcon: "Icon image for a resource" src: str mime_type: str | None = None sizes: list[str] | None = None - theme: ToolExecutionCompleteDataResultContentsItemIconsItemTheme | None = None + theme: ToolExecutionCompleteContentResourceLinkIconTheme | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItemIconsItem": + def from_dict(obj: Any) -> "ToolExecutionCompleteContentResourceLinkIcon": assert isinstance(obj, dict) src = from_str(obj.get("src")) mime_type = from_union([from_none, from_str], obj.get("mimeType")) sizes = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("sizes")) - theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], obj.get("theme")) - return ToolExecutionCompleteDataResultContentsItemIconsItem( + theme = from_union([from_none, lambda x: parse_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], obj.get("theme")) + return ToolExecutionCompleteContentResourceLinkIcon( src=src, mime_type=mime_type, sizes=sizes, @@ -2146,20 +2146,20 @@ def to_dict(self) -> dict: if self.sizes is not None: result["sizes"] = from_union([from_none, lambda x: from_list(from_str, x)], self.sizes) if self.theme is not None: - result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteDataResultContentsItemIconsItemTheme, x)], self.theme) + result["theme"] = from_union([from_none, lambda x: to_enum(ToolExecutionCompleteContentResourceLinkIconTheme, x)], self.theme) return result @dataclass -class ToolExecutionCompleteDataResultContentsItem: +class ToolExecutionCompleteResultContentsItem: "A content block within a tool result, which may be text, terminal output, image, audio, or a resource" - type: ToolExecutionCompleteDataResultContentsItemType + type: ToolExecutionCompleteResultContentsItemType text: str | None = None exit_code: float | None = None cwd: str | None = None data: str | None = None mime_type: str | None = None - icons: list[ToolExecutionCompleteDataResultContentsItemIconsItem] | None = None + icons: list[ToolExecutionCompleteContentResourceLinkIcon] | None = None name: str | None = None title: str | None = None uri: str | None = None @@ -2168,22 +2168,22 @@ class ToolExecutionCompleteDataResultContentsItem: resource: Any = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": + def from_dict(obj: Any) -> "ToolExecutionCompleteResultContentsItem": assert isinstance(obj, dict) - type = parse_enum(ToolExecutionCompleteDataResultContentsItemType, obj.get("type")) + type = parse_enum(ToolExecutionCompleteResultContentsItemType, obj.get("type")) text = from_union([from_none, from_str], obj.get("text")) exit_code = from_union([from_none, from_float], obj.get("exitCode")) cwd = from_union([from_none, from_str], obj.get("cwd")) data = from_union([from_none, from_str], obj.get("data")) mime_type = from_union([from_none, from_str], obj.get("mimeType")) - icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItemIconsItem.from_dict, x)], obj.get("icons")) + icons = from_union([from_none, lambda x: from_list(ToolExecutionCompleteContentResourceLinkIcon.from_dict, x)], obj.get("icons")) name = from_union([from_none, from_str], obj.get("name")) title = from_union([from_none, from_str], obj.get("title")) uri = from_union([from_none, from_str], obj.get("uri")) description = from_union([from_none, from_str], obj.get("description")) size = from_union([from_none, from_float], obj.get("size")) resource = obj.get("resource") - return ToolExecutionCompleteDataResultContentsItem( + return ToolExecutionCompleteResultContentsItem( type=type, text=text, exit_code=exit_code, @@ -2201,7 +2201,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteDataResultContentsItem": def to_dict(self) -> dict: result: dict = {} - result["type"] = to_enum(ToolExecutionCompleteDataResultContentsItemType, self.type) + result["type"] = to_enum(ToolExecutionCompleteResultContentsItemType, self.type) if self.text is not None: result["text"] = from_union([from_none, from_str], self.text) if self.exit_code is not None: @@ -2213,7 +2213,7 @@ def to_dict(self) -> dict: if self.mime_type is not None: result["mimeType"] = from_union([from_none, from_str], self.mime_type) if self.icons is not None: - result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItemIconsItem, x), x)], self.icons) + result["icons"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteContentResourceLinkIcon, x), x)], self.icons) if self.name is not None: result["name"] = from_union([from_none, from_str], self.name) if self.title is not None: @@ -2230,19 +2230,19 @@ def to_dict(self) -> dict: @dataclass -class ToolExecutionCompleteDataResult: +class ToolExecutionCompleteResult: "Tool execution result on success" content: str detailed_content: str | None = None - contents: list[ToolExecutionCompleteDataResultContentsItem] | None = None + contents: list[ToolExecutionCompleteResultContentsItem] | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataResult": + def from_dict(obj: Any) -> "ToolExecutionCompleteResult": assert isinstance(obj, dict) content = from_str(obj.get("content")) detailed_content = from_union([from_none, from_str], obj.get("detailedContent")) - contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteDataResultContentsItem.from_dict, x)], obj.get("contents")) - return ToolExecutionCompleteDataResult( + contents = from_union([from_none, lambda x: from_list(ToolExecutionCompleteResultContentsItem.from_dict, x)], obj.get("contents")) + return ToolExecutionCompleteResult( content=content, detailed_content=detailed_content, contents=contents, @@ -2254,22 +2254,22 @@ def to_dict(self) -> dict: if self.detailed_content is not None: result["detailedContent"] = from_union([from_none, from_str], self.detailed_content) if self.contents is not None: - result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteDataResultContentsItem, x), x)], self.contents) + result["contents"] = from_union([from_none, lambda x: from_list(lambda x: to_class(ToolExecutionCompleteResultContentsItem, x), x)], self.contents) return result @dataclass -class ToolExecutionCompleteDataError: +class ToolExecutionCompleteError: "Error details when the tool execution failed" message: str code: str | None = None @staticmethod - def from_dict(obj: Any) -> "ToolExecutionCompleteDataError": + def from_dict(obj: Any) -> "ToolExecutionCompleteError": assert isinstance(obj, dict) message = from_str(obj.get("message")) code = from_union([from_none, from_str], obj.get("code")) - return ToolExecutionCompleteDataError( + return ToolExecutionCompleteError( message=message, code=code, ) @@ -2290,8 +2290,8 @@ class ToolExecutionCompleteData: model: str | None = None interaction_id: str | None = None is_user_requested: bool | None = None - result: ToolExecutionCompleteDataResult | None = None - error: ToolExecutionCompleteDataError | None = None + result: ToolExecutionCompleteResult | None = None + error: ToolExecutionCompleteError | None = None tool_telemetry: dict[str, Any] | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @@ -2304,8 +2304,8 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": model = from_union([from_none, from_str], obj.get("model")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) is_user_requested = from_union([from_none, from_bool], obj.get("isUserRequested")) - result = from_union([from_none, ToolExecutionCompleteDataResult.from_dict], obj.get("result")) - error = from_union([from_none, ToolExecutionCompleteDataError.from_dict], obj.get("error")) + result = from_union([from_none, ToolExecutionCompleteResult.from_dict], obj.get("result")) + error = from_union([from_none, ToolExecutionCompleteError.from_dict], obj.get("error")) tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) return ToolExecutionCompleteData( @@ -2331,9 +2331,9 @@ def to_dict(self) -> dict: if self.is_user_requested is not None: result["isUserRequested"] = from_union([from_none, from_bool], self.is_user_requested) if self.result is not None: - result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataResult, x)], self.result) + result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteResult, x)], self.result) if self.error is not None: - result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteDataError, x)], self.error) + result["error"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteError, x)], self.error) if self.tool_telemetry is not None: result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) if self.parent_tool_call_id is not None: @@ -2585,17 +2585,17 @@ def to_dict(self) -> dict: @dataclass -class HookEndDataError: +class HookEndError: "Error details when the hook failed" message: str stack: str | None = None @staticmethod - def from_dict(obj: Any) -> "HookEndDataError": + def from_dict(obj: Any) -> "HookEndError": assert isinstance(obj, dict) message = from_str(obj.get("message")) stack = from_union([from_none, from_str], obj.get("stack")) - return HookEndDataError( + return HookEndError( message=message, stack=stack, ) @@ -2615,7 +2615,7 @@ class HookEndData: hook_type: str success: bool output: Any = None - error: HookEndDataError | None = None + error: HookEndError | None = None @staticmethod def from_dict(obj: Any) -> "HookEndData": @@ -2624,7 +2624,7 @@ def from_dict(obj: Any) -> "HookEndData": hook_type = from_str(obj.get("hookType")) success = from_bool(obj.get("success")) output = obj.get("output") - error = from_union([from_none, HookEndDataError.from_dict], obj.get("error")) + error = from_union([from_none, HookEndError.from_dict], obj.get("error")) return HookEndData( hook_invocation_id=hook_invocation_id, hook_type=hook_type, @@ -2641,22 +2641,22 @@ def to_dict(self) -> dict: if self.output is not None: result["output"] = self.output if self.error is not None: - result["error"] = from_union([from_none, lambda x: to_class(HookEndDataError, x)], self.error) + result["error"] = from_union([from_none, lambda x: to_class(HookEndError, x)], self.error) return result @dataclass -class SystemMessageDataMetadata: +class SystemMessageMetadata: "Metadata about the prompt template and its construction" prompt_version: str | None = None variables: dict[str, Any] | None = None @staticmethod - def from_dict(obj: Any) -> "SystemMessageDataMetadata": + def from_dict(obj: Any) -> "SystemMessageMetadata": assert isinstance(obj, dict) prompt_version = from_union([from_none, from_str], obj.get("promptVersion")) variables = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("variables")) - return SystemMessageDataMetadata( + return SystemMessageMetadata( prompt_version=prompt_version, variables=variables, ) @@ -2676,7 +2676,7 @@ class SystemMessageData: content: str role: SystemMessageDataRole name: str | None = None - metadata: SystemMessageDataMetadata | None = None + metadata: SystemMessageMetadata | None = None @staticmethod def from_dict(obj: Any) -> "SystemMessageData": @@ -2684,7 +2684,7 @@ def from_dict(obj: Any) -> "SystemMessageData": content = from_str(obj.get("content")) role = parse_enum(SystemMessageDataRole, obj.get("role")) name = from_union([from_none, from_str], obj.get("name")) - metadata = from_union([from_none, SystemMessageDataMetadata.from_dict], obj.get("metadata")) + metadata = from_union([from_none, SystemMessageMetadata.from_dict], obj.get("metadata")) return SystemMessageData( content=content, role=role, @@ -2699,7 +2699,7 @@ def to_dict(self) -> dict: if self.name is not None: result["name"] = from_union([from_none, from_str], self.name) if self.metadata is not None: - result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageDataMetadata, x)], self.metadata) + result["metadata"] = from_union([from_none, lambda x: to_class(SystemMessageMetadata, x)], self.metadata) return result @@ -2803,14 +2803,14 @@ def to_dict(self) -> dict: @dataclass -class PermissionRequestShellPossibleURL: +class PermissionRequestShellPossibleUrl: url: str @staticmethod - def from_dict(obj: Any) -> "PermissionRequestShellPossibleURL": + def from_dict(obj: Any) -> "PermissionRequestShellPossibleUrl": assert isinstance(obj, dict) url = from_str(obj.get("url")) - return PermissionRequestShellPossibleURL( + return PermissionRequestShellPossibleUrl( url=url, ) @@ -2829,7 +2829,7 @@ class PermissionRequest: intention: str | None = None commands: list[PermissionRequestShellCommand] | None = None possible_paths: list[str] | None = None - possible_urls: list[PermissionRequestShellPossibleURL] | None = None + possible_urls: list[PermissionRequestShellPossibleUrl] | None = None has_write_file_redirection: bool | None = None can_offer_session_approval: bool | None = None warning: str | None = None @@ -2862,7 +2862,7 @@ def from_dict(obj: Any) -> "PermissionRequest": intention = from_union([from_none, from_str], obj.get("intention")) commands = from_union([from_none, lambda x: from_list(PermissionRequestShellCommand.from_dict, x)], obj.get("commands")) possible_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("possiblePaths")) - possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleURL.from_dict, x)], obj.get("possibleUrls")) + possible_urls = from_union([from_none, lambda x: from_list(PermissionRequestShellPossibleUrl.from_dict, x)], obj.get("possibleUrls")) has_write_file_redirection = from_union([from_none, from_bool], obj.get("hasWriteFileRedirection")) can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) warning = from_union([from_none, from_str], obj.get("warning")) @@ -2931,7 +2931,7 @@ def to_dict(self) -> dict: if self.possible_paths is not None: result["possiblePaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.possible_paths) if self.possible_urls is not None: - result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleURL, x), x)], self.possible_urls) + result["possibleUrls"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRequestShellPossibleUrl, x), x)], self.possible_urls) if self.has_write_file_redirection is not None: result["hasWriteFileRedirection"] = from_union([from_none, from_bool], self.has_write_file_redirection) if self.can_offer_session_approval is not None: @@ -3008,21 +3008,21 @@ def to_dict(self) -> dict: @dataclass -class PermissionCompletedDataResult: +class PermissionCompletedResult: "The result of the permission request" - kind: PermissionCompletedKind + kind: PermissionCompletedResultKind @staticmethod - def from_dict(obj: Any) -> "PermissionCompletedDataResult": + def from_dict(obj: Any) -> "PermissionCompletedResult": assert isinstance(obj, dict) - kind = parse_enum(PermissionCompletedKind, obj.get("kind")) - return PermissionCompletedDataResult( + kind = parse_enum(PermissionCompletedResultKind, obj.get("kind")) + return PermissionCompletedResult( kind=kind, ) def to_dict(self) -> dict: result: dict = {} - result["kind"] = to_enum(PermissionCompletedKind, self.kind) + result["kind"] = to_enum(PermissionCompletedResultKind, self.kind) return result @@ -3030,13 +3030,13 @@ def to_dict(self) -> dict: class PermissionCompletedData: "Permission request completion notification signaling UI dismissal" request_id: str - result: PermissionCompletedDataResult + result: PermissionCompletedResult @staticmethod def from_dict(obj: Any) -> "PermissionCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - result = PermissionCompletedDataResult.from_dict(obj.get("result")) + result = PermissionCompletedResult.from_dict(obj.get("result")) return PermissionCompletedData( request_id=request_id, result=result, @@ -3045,7 +3045,7 @@ def from_dict(obj: Any) -> "PermissionCompletedData": def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionCompletedDataResult, self.result) + result["result"] = to_class(PermissionCompletedResult, self.result) return result @@ -3268,17 +3268,17 @@ def to_dict(self) -> dict: @dataclass -class MCPOauthRequiredStaticClientConfig: +class McpOauthRequiredStaticClientConfig: "Static OAuth client configuration, if the server specifies one" client_id: str public_client: bool | None = None @staticmethod - def from_dict(obj: Any) -> "MCPOauthRequiredStaticClientConfig": + def from_dict(obj: Any) -> "McpOauthRequiredStaticClientConfig": assert isinstance(obj, dict) client_id = from_str(obj.get("clientId")) public_client = from_union([from_none, from_bool], obj.get("publicClient")) - return MCPOauthRequiredStaticClientConfig( + return McpOauthRequiredStaticClientConfig( client_id=client_id, public_client=public_client, ) @@ -3297,7 +3297,7 @@ class McpOauthRequiredData: request_id: str server_name: str server_url: str - static_client_config: MCPOauthRequiredStaticClientConfig | None = None + static_client_config: McpOauthRequiredStaticClientConfig | None = None @staticmethod def from_dict(obj: Any) -> "McpOauthRequiredData": @@ -3305,7 +3305,7 @@ def from_dict(obj: Any) -> "McpOauthRequiredData": request_id = from_str(obj.get("requestId")) server_name = from_str(obj.get("serverName")) server_url = from_str(obj.get("serverUrl")) - static_client_config = from_union([from_none, MCPOauthRequiredStaticClientConfig.from_dict], obj.get("staticClientConfig")) + static_client_config = from_union([from_none, McpOauthRequiredStaticClientConfig.from_dict], obj.get("staticClientConfig")) return McpOauthRequiredData( request_id=request_id, server_name=server_name, @@ -3319,7 +3319,7 @@ def to_dict(self) -> dict: result["serverName"] = from_str(self.server_name) result["serverUrl"] = from_str(self.server_url) if self.static_client_config is not None: - result["staticClientConfig"] = from_union([from_none, lambda x: to_class(MCPOauthRequiredStaticClientConfig, x)], self.static_client_config) + result["staticClientConfig"] = from_union([from_none, lambda x: to_class(McpOauthRequiredStaticClientConfig, x)], self.static_client_config) return result @@ -3796,20 +3796,20 @@ def to_dict(self) -> dict: @dataclass -class MCPServersLoadedServer: +class McpServersLoadedServer: name: str - status: MCPServerStatus + status: McpServersLoadedServerStatus source: str | None = None error: str | None = None @staticmethod - def from_dict(obj: Any) -> "MCPServersLoadedServer": + def from_dict(obj: Any) -> "McpServersLoadedServer": assert isinstance(obj, dict) name = from_str(obj.get("name")) - status = parse_enum(MCPServerStatus, obj.get("status")) + status = parse_enum(McpServersLoadedServerStatus, obj.get("status")) source = from_union([from_none, from_str], obj.get("source")) error = from_union([from_none, from_str], obj.get("error")) - return MCPServersLoadedServer( + return McpServersLoadedServer( name=name, status=status, source=source, @@ -3819,7 +3819,7 @@ def from_dict(obj: Any) -> "MCPServersLoadedServer": def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) - result["status"] = to_enum(MCPServerStatus, self.status) + result["status"] = to_enum(McpServersLoadedServerStatus, self.status) if self.source is not None: result["source"] = from_union([from_none, from_str], self.source) if self.error is not None: @@ -3829,19 +3829,19 @@ def to_dict(self) -> dict: @dataclass class SessionMcpServersLoadedData: - servers: list[MCPServersLoadedServer] + servers: list[McpServersLoadedServer] @staticmethod def from_dict(obj: Any) -> "SessionMcpServersLoadedData": assert isinstance(obj, dict) - servers = from_list(MCPServersLoadedServer.from_dict, obj.get("servers")) + servers = from_list(McpServersLoadedServer.from_dict, obj.get("servers")) return SessionMcpServersLoadedData( servers=servers, ) def to_dict(self) -> dict: result: dict = {} - result["servers"] = from_list(lambda x: to_class(MCPServersLoadedServer, x), self.servers) + result["servers"] = from_list(lambda x: to_class(McpServersLoadedServer, x), self.servers) return result @@ -3982,7 +3982,7 @@ class AssistantMessageToolRequestType(Enum): CUSTOM = "custom" -class ToolExecutionCompleteDataResultContentsItemType(Enum): +class ToolExecutionCompleteResultContentsItemType(Enum): "A content block within a tool result, which may be text, terminal output, image, audio, or a resource discriminator" TEXT = "text" TERMINAL = "terminal" @@ -3992,7 +3992,7 @@ class ToolExecutionCompleteDataResultContentsItemType(Enum): RESOURCE = "resource" -class ToolExecutionCompleteDataResultContentsItemIconsItemTheme(Enum): +class ToolExecutionCompleteContentResourceLinkIconTheme(Enum): "Theme variant this icon is intended for" LIGHT = "light" DARK = "dark" @@ -4042,7 +4042,7 @@ class PermissionRequestMemoryDirection(Enum): DOWNVOTE = "downvote" -class PermissionCompletedKind(Enum): +class PermissionCompletedResultKind(Enum): "The outcome of the permission request" APPROVED = "approved" DENIED_BY_RULES = "denied-by-rules" @@ -4065,7 +4065,7 @@ class ElicitationCompletedAction(Enum): CANCEL = "cancel" -class MCPServerStatus(Enum): +class McpServersLoadedServerStatus(Enum): "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" CONNECTED = "connected" FAILED = "failed" diff --git a/python/copilot/session.py b/python/copilot/session.py index ac771923a..6a0d8844b 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -24,12 +24,12 @@ from .generated.rpc import ( ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, - Kind, + PermissionDecisionKind, LogRequest, ModelSwitchToRequest, PermissionDecision, PermissionDecisionRequest, - RequestedSchemaType, + UIElicitationSchemaType, SessionFsHandler, SessionLogLevel, SessionRpc, @@ -43,7 +43,7 @@ UIElicitationSchemaPropertyNumberType, UIHandlePendingElicitationRequest, ) -from .generated.rpc import ModelCapabilitiesClass as _RpcModelCapabilitiesOverride +from .generated.rpc import ModelCapabilitiesOverride as _RpcModelCapabilitiesOverride from .generated.session_events import ( AssistantMessageData, CapabilitiesChangedData, @@ -471,7 +471,7 @@ async def confirm(self, message: str) -> bool: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=RequestedSchemaType.OBJECT, + type=UIElicitationSchemaType.OBJECT, properties={ "confirmed": UIElicitationSchemaProperty( type=UIElicitationSchemaPropertyNumberType.BOOLEAN, @@ -506,7 +506,7 @@ async def select(self, message: str, options: list[str]) -> str | None: UIElicitationRequest( message=message, requested_schema=UIElicitationSchema( - type=RequestedSchemaType.OBJECT, + type=UIElicitationSchemaType.OBJECT, properties={ "selection": UIElicitationSchemaProperty( type=UIElicitationSchemaPropertyNumberType.STRING, @@ -1436,7 +1436,7 @@ async def _execute_permission_and_respond( return perm_result = PermissionDecision( - kind=Kind(result.kind), + kind=PermissionDecisionKind(result.kind), rules=result.rules, feedback=result.feedback, message=result.message, @@ -1455,7 +1455,7 @@ async def _execute_permission_and_respond( PermissionDecisionRequest( request_id=request_id, result=PermissionDecision( - kind=Kind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, + kind=PermissionDecisionKind.DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER, ), ) ) diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 10ecc5bef..b887a1900 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -160,6 +160,10 @@ function schemaTypeToCSharp(schema: JSONSchema7, required: boolean, knownTypes: const refName = schema.$ref.split("/").pop()!; return knownTypes.get(refName) || refName; } + // Titled union schemas (anyOf with a title) — use the title if it's a known generated type + if (schema.title && schema.anyOf && knownTypes.has(schema.title)) { + return required ? schema.title : `${schema.title}?`; + } const type = schema.type; const format = schema.format; // Handle type: ["string", "null"] patterns (nullable string) @@ -567,7 +571,14 @@ function resolveSessionPropertyType( // Discriminated union: anyOf with multiple object variants sharing a const discriminator const nonNull = propSchema.anyOf.filter((s) => typeof s === "object" && s !== null && (s as JSONSchema7).type !== "null"); if (nonNull.length > 1) { - const variants = nonNull as JSONSchema7[]; + // Resolve $ref variants to their actual schemas + const variants = (nonNull as JSONSchema7[]).map((v) => { + if (v.$ref) { + const resolved = resolveRef(v.$ref, sessionDefinitions); + return resolved ?? v; + } + return v; + }); const discriminatorInfo = findDiscriminator(variants); if (discriminatorInfo) { const hasNull = propSchema.anyOf.length > nonNull.length; @@ -602,6 +613,19 @@ function resolveSessionPropertyType( ); return isRequired ? `${itemType}[]` : `${itemType}[]?`; } + if (propSchema.type === "object" && propSchema.additionalProperties && typeof propSchema.additionalProperties === "object") { + const valueSchema = propSchema.additionalProperties as JSONSchema7; + const valueType = resolveSessionPropertyType( + valueSchema, + parentClassName, + `${propName}Value`, + true, + knownTypes, + nestedClasses, + enumOutput + ); + return isRequired ? `IDictionary` : `IDictionary?`; + } return schemaTypeToCSharp(propSchema, isRequired, knownTypes); } @@ -859,6 +883,32 @@ function resolveRpcType(schema: JSONSchema7, isRequired: boolean, parentClassNam if (nullableInner) { return resolveRpcType(nullableInner, false, parentClassName, propName, classes); } + // Discriminated union: anyOf with multiple variants sharing a const discriminator + if (schema.anyOf && Array.isArray(schema.anyOf)) { + const nonNull = schema.anyOf.filter((s) => typeof s === "object" && s !== null && (s as JSONSchema7).type !== "null"); + if (nonNull.length > 1) { + const variants = (nonNull as JSONSchema7[]).map((v) => { + if (v.$ref) { + const resolved = resolveRef(v.$ref, rpcDefinitions); + return resolved ?? v; + } + return v; + }); + const discriminatorInfo = findDiscriminator(variants); + if (discriminatorInfo) { + const hasNull = schema.anyOf.length > nonNull.length; + const baseClassName = (schema.title as string) ?? `${parentClassName}${propName}`; + if (!emittedRpcClassSchemas.has(baseClassName)) { + emittedRpcClassSchemas.set(baseClassName, "polymorphic"); + const nestedMap = new Map(); + const polymorphicCode = generatePolymorphicClasses(baseClassName, discriminatorInfo.property, variants, rpcKnownTypes, nestedMap, rpcEnumOutput, schema.description); + classes.push(polymorphicCode); + for (const nested of nestedMap.values()) classes.push(nested); + } + return isRequired && !hasNull ? baseClassName : `${baseClassName}?`; + } + } + } // Handle enums (string unions like "interactive" | "plan" | "autopilot") if (schema.enum && Array.isArray(schema.enum)) { const enumName = getOrCreateEnum( diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 36f5ff0d5..ffa61736b 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -1624,15 +1624,19 @@ async function generateRpc(schemaPath?: string): Promise { $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, }; - // Generate types via quicktype + // Generate types via quicktype — use a single combined schema source to avoid + // quicktype inventing Purple/Fluffy disambiguation prefixes for shared types const schemaInput = new JSONSchemaInput(new FetchingJSONSchemaStore()); - for (const [name, def] of Object.entries(rootDefinitions)) { - const schemaWithDefs = withSharedDefinitions( - typeof def === "object" ? (def as JSONSchema7) : {}, - allDefinitionCollections - ); - await schemaInput.addSource({ name, schema: JSON.stringify(schemaWithDefs) }); - } + const singleSchema: Record = { + $schema: "http://json-schema.org/draft-07/schema#", + type: "object", + definitions: allDefinitions, + properties: Object.fromEntries( + Object.keys(rootDefinitions).map((name) => [name, { $ref: `#/definitions/${name}` }]) + ), + required: Object.keys(rootDefinitions), + }; + await schemaInput.addSource({ name: "RPC", schema: JSON.stringify(singleSchema) }); const inputData = new InputData(); inputData.addInput(schemaInput); From 9b8a314d3d327c63d5b8b61b966358d6a3a3ca2c Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 18:48:28 +0100 Subject: [PATCH 07/10] Logging for C# e2e tests --- dotnet/test/Harness/E2ETestBase.cs | 21 +++++++++++++++++++++ dotnet/test/Harness/E2ETestContext.cs | 5 +++++ 2 files changed, 26 insertions(+) diff --git a/dotnet/test/Harness/E2ETestBase.cs b/dotnet/test/Harness/E2ETestBase.cs index d1756ea61..46162b50f 100644 --- a/dotnet/test/Harness/E2ETestBase.cs +++ b/dotnet/test/Harness/E2ETestBase.cs @@ -5,6 +5,7 @@ using System.Data; using System.Reflection; using GitHub.Copilot.SDK.Test.Harness; +using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; @@ -24,6 +25,26 @@ protected E2ETestBase(E2ETestFixture fixture, string snapshotCategory, ITestOutp _fixture = fixture; _snapshotCategory = snapshotCategory; _testName = GetTestName(output); + Logger = new XunitLogger(output); + + // Wire logger into the shared context so all clients created via Ctx.CreateClient get it. + Ctx.Logger = Logger; + } + + /// Logger that forwards warnings and above to xunit test output. + protected ILogger Logger { get; } + + /// Bridges to xunit's . + private sealed class XunitLogger(ITestOutputHelper output) : ILogger + { + public IDisposable? BeginScope(TState state) where TState : notnull => null; + public bool IsEnabled(LogLevel logLevel) => logLevel >= LogLevel.Warning; + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) return; + try { output.WriteLine($"[{logLevel}] {formatter(state, exception)}"); } + catch (InvalidOperationException) { /* test already finished */ } + } } private static string GetTestName(ITestOutputHelper output) diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 47c8b2c4d..7b47ab0b7 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using System.Text.RegularExpressions; +using Microsoft.Extensions.Logging; namespace GitHub.Copilot.SDK.Test.Harness; @@ -13,6 +14,9 @@ public sealed class E2ETestContext : IAsyncDisposable public string WorkDir { get; } public string ProxyUrl { get; } + /// Optional logger injected by tests; applied to all clients created via . + public ILogger? Logger { get; set; } + private readonly CapiProxy _proxy; private readonly string _repoRoot; @@ -99,6 +103,7 @@ public CopilotClient CreateClient(bool useStdio = true, CopilotClientOptions? op options.Cwd ??= WorkDir; options.Environment ??= GetEnvironment(); options.UseStdio = useStdio; + options.Logger ??= Logger; if (string.IsNullOrEmpty(options.CliUrl)) { From 240ceda98a58d31b8c9296ae379fcc396e416ae2 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 19:01:28 +0100 Subject: [PATCH 08/10] Fix C# SessionFs tests --- dotnet/src/Client.cs | 2 + dotnet/test/SessionFsTests.cs | 205 +++++++++++++++++++++++----------- 2 files changed, 139 insertions(+), 68 deletions(-) diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 668d090f5..993df427e 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using StreamJsonRpc; +using StreamJsonRpc.Protocol; using System.Collections.Concurrent; using System.Data; using System.Diagnostics; @@ -1836,6 +1837,7 @@ private static LogLevel MapLevel(TraceEventType eventType) AllowOutOfOrderMetadataProperties = true, NumberHandling = JsonNumberHandling.AllowReadingFromString, DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)] + [JsonSerializable(typeof(CommonErrorData))] [JsonSerializable(typeof(CreateSessionRequest))] [JsonSerializable(typeof(CreateSessionResponse))] [JsonSerializable(typeof(CustomAgentConfig))] diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs index 1a89f4c09..e7bef3203 100644 --- a/dotnet/test/SessionFsTests.cs +++ b/dotnet/test/SessionFsTests.cs @@ -435,8 +435,19 @@ private sealed class TestSessionFsHandler(string sessionId, string rootDir) : IS { public async Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default) { - var content = await File.ReadAllTextAsync(ResolvePath(request.Path), cancellationToken); - return new SessionFsReadFileResult { Content = content }; + try + { + var content = await File.ReadAllTextAsync(ResolvePath(request.Path), cancellationToken); + return new SessionFsReadFileResult { Content = content }; + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { + return new SessionFsReadFileResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; + } + catch (Exception ex) + { + return new SessionFsReadFileResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; + } } public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) @@ -455,105 +466,163 @@ public async Task ReadFileAsync(SessionFsReadFileReques return null; } - public Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) + public async Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) { - var fullPath = ResolvePath(request.Path); - return Task.FromResult(new SessionFsExistsResult + await Task.CompletedTask; + try { - Exists = File.Exists(fullPath) || Directory.Exists(fullPath), - }); + var fullPath = ResolvePath(request.Path); + return new SessionFsExistsResult + { + Exists = File.Exists(fullPath) || Directory.Exists(fullPath), + }; + } + catch + { + return new SessionFsExistsResult { Exists = false }; + } } - public Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default) + public async Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default) { - var fullPath = ResolvePath(request.Path); - if (File.Exists(fullPath)) + await Task.CompletedTask; + try { - var info = new FileInfo(fullPath); - return Task.FromResult(new SessionFsStatResult + var fullPath = ResolvePath(request.Path); + if (File.Exists(fullPath)) { - IsFile = true, - IsDirectory = false, - Size = info.Length, - Mtime = info.LastWriteTimeUtc, - Birthtime = info.CreationTimeUtc, - }); - } + var info = new FileInfo(fullPath); + return new SessionFsStatResult + { + IsFile = true, + IsDirectory = false, + Size = info.Length, + Mtime = info.LastWriteTimeUtc, + Birthtime = info.CreationTimeUtc, + }; + } - var dirInfo = new DirectoryInfo(fullPath); - if (!dirInfo.Exists) + var dirInfo = new DirectoryInfo(fullPath); + if (!dirInfo.Exists) + { + return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = $"Path does not exist: {request.Path}" } }; + } + + return new SessionFsStatResult + { + IsFile = false, + IsDirectory = true, + Size = 0, + Mtime = dirInfo.LastWriteTimeUtc, + Birthtime = dirInfo.CreationTimeUtc, + }; + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) { - throw new FileNotFoundException($"Path does not exist: {request.Path}"); + return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; } - - return Task.FromResult(new SessionFsStatResult + catch (Exception ex) { - IsFile = false, - IsDirectory = true, - Size = 0, - Mtime = dirInfo.LastWriteTimeUtc, - Birthtime = dirInfo.CreationTimeUtc, - }); + return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; + } } - public Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) + public async Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) { + await Task.CompletedTask; Directory.CreateDirectory(ResolvePath(request.Path)); - return Task.FromResult(null); + return null; } - public Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) + public async Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(Path.GetFileName) - .Where(name => name is not null) - .Cast() - .ToList(); + await Task.CompletedTask; + try + { + var entries = Directory + .EnumerateFileSystemEntries(ResolvePath(request.Path)) + .Select(Path.GetFileName) + .Where(name => name is not null) + .Cast() + .ToList(); + + return new SessionFsReaddirResult { Entries = entries }; + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { + return new SessionFsReaddirResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; + } + catch (Exception ex) + { + return new SessionFsReaddirResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; + } + } - return Task.FromResult(new SessionFsReaddirResult { Entries = entries }); + public async Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + try + { + var entries = Directory + .EnumerateFileSystemEntries(ResolvePath(request.Path)) + .Select(path => new SessionFsReaddirWithTypesEntry + { + Name = Path.GetFileName(path), + Type = Directory.Exists(path) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, + }) + .ToList(); + + return new SessionFsReaddirWithTypesResult { Entries = entries }; + } + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + { + return new SessionFsReaddirWithTypesResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; + } + catch (Exception ex) + { + return new SessionFsReaddirWithTypesResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; + } } - public Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default) + public async Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(path => new SessionFsReaddirWithTypesEntry + await Task.CompletedTask; + try + { + var fullPath = ResolvePath(request.Path); + + if (File.Exists(fullPath)) { - Name = Path.GetFileName(path), - Type = Directory.Exists(path) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, - }) - .ToList(); + File.Delete(fullPath); + return null; + } - return Task.FromResult(new SessionFsReaddirWithTypesResult { Entries = entries }); - } + if (Directory.Exists(fullPath)) + { + Directory.Delete(fullPath, request.Recursive ?? false); + return null; + } - public Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) - { - var fullPath = ResolvePath(request.Path); + if (request.Force == true) + { + return null; + } - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - return Task.FromResult(null); + return new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = $"Path does not exist: {request.Path}" }; } - - if (Directory.Exists(fullPath)) + catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) { - Directory.Delete(fullPath, request.Recursive ?? false); - return Task.FromResult(null); + return new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message }; } - - if (request.Force == true) + catch (Exception ex) { - return Task.FromResult(null); + return new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message }; } - - throw new FileNotFoundException($"Path does not exist: {request.Path}"); } - public Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) + public async Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) { + await Task.CompletedTask; var src = ResolvePath(request.Src); var dest = ResolvePath(request.Dest); Directory.CreateDirectory(Path.GetDirectoryName(dest)!); @@ -567,7 +636,7 @@ public Task ReaddirWithTypesAsync(SessionFsRead File.Move(src, dest, overwrite: true); } - return Task.FromResult(null); + return null; } private string ResolvePath(string sessionPath) From 24a4b71111d8cccd43c1251b707a007e3b891759 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 19:53:58 +0100 Subject: [PATCH 09/10] Add idiomatic SessionFsProvider abstractions for all 4 SDK languages Introduce a SessionFsProvider base class/interface in each language that wraps the generated SessionFsHandler RPC contract with idiomatic patterns: - C#: Abstract class with virtual methods that throw on error - Go: Interface with (value, error) returns; unexported adapter - TypeScript: Interface + createSessionFsAdapter() wrapper function - Python: ABC + create_session_fs_adapter() wrapper function Each adapter catches exceptions/errors and converts them to SessionFsError results (ENOENT/UNKNOWN). Public APIs now use SessionFsProvider exclusively. Also fixes C# serialization crash (CommonErrorData not registered in source-generated JSON context) and adds shared Warning-level logging to all C# E2E tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/src/Client.cs | 2 +- dotnet/src/SessionFsProvider.cs | 213 +++++++++++++++++++++++ dotnet/src/Types.cs | 4 +- dotnet/test/SessionFsTests.cs | 239 +++++++++----------------- go/client.go | 4 +- go/internal/e2e/session_fs_test.go | 124 ++++++------- go/session_fs_provider.go | 156 +++++++++++++++++ go/types.go | 4 +- nodejs/src/client.ts | 5 +- nodejs/src/index.ts | 4 +- nodejs/src/sessionFsProvider.ts | 159 +++++++++++++++++ nodejs/src/types.ts | 8 +- nodejs/test/e2e/session_fs.test.ts | 135 +++++---------- python/copilot/__init__.py | 10 +- python/copilot/client.py | 5 +- python/copilot/session.py | 4 +- python/copilot/session_fs_provider.py | 231 +++++++++++++++++++++++++ python/e2e/test_session_fs.py | 129 +++++++------- 18 files changed, 1024 insertions(+), 412 deletions(-) create mode 100644 dotnet/src/SessionFsProvider.cs create mode 100644 go/session_fs_provider.go create mode 100644 nodejs/src/sessionFsProvider.ts create mode 100644 python/copilot/session_fs_provider.py diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 993df427e..85fd91db1 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1105,7 +1105,7 @@ await Rpc.SessionFs.SetProviderAsync( cancellationToken); } - private void ConfigureSessionFsHandlers(CopilotSession session, Func? createSessionFsHandler) + private void ConfigureSessionFsHandlers(CopilotSession session, Func? createSessionFsHandler) { if (_options.SessionFs is null) { diff --git a/dotnet/src/SessionFsProvider.cs b/dotnet/src/SessionFsProvider.cs new file mode 100644 index 000000000..be5b9e25c --- /dev/null +++ b/dotnet/src/SessionFsProvider.cs @@ -0,0 +1,213 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +using GitHub.Copilot.SDK.Rpc; + +namespace GitHub.Copilot.SDK; + +/// +/// Base class for session filesystem providers. Subclasses override the +/// virtual methods and use normal C# patterns (return values, throw exceptions). +/// The base class catches exceptions and converts them to +/// results expected by the runtime. +/// +public abstract class SessionFsProvider : ISessionFsHandler +{ + /// Reads the full content of a file. Throw if the file does not exist. + /// SessionFs-relative path. + /// Cancellation token. + /// The file content as a UTF-8 string. + protected abstract Task ReadFileAsync(string path, CancellationToken cancellationToken); + + /// Writes content to a file, creating it (and parent directories) if needed. + /// SessionFs-relative path. + /// Content to write. + /// Cancellation token. + protected abstract Task WriteFileAsync(string path, string content, CancellationToken cancellationToken); + + /// Appends content to a file, creating it (and parent directories) if needed. + /// SessionFs-relative path. + /// Content to append. + /// Cancellation token. + protected abstract Task AppendFileAsync(string path, string content, CancellationToken cancellationToken); + + /// Checks whether a path exists. + /// SessionFs-relative path. + /// Cancellation token. + /// true if the path exists, false otherwise. + protected abstract Task ExistsAsync(string path, CancellationToken cancellationToken); + + /// Gets metadata about a file or directory. Throw if the path does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task StatAsync(string path, CancellationToken cancellationToken); + + /// Creates a directory (and optionally parents). Does not fail if it already exists. + /// SessionFs-relative path. + /// Whether to create parent directories. + /// Cancellation token. + protected abstract Task MkdirAsync(string path, bool recursive, CancellationToken cancellationToken); + + /// Lists entry names in a directory. Throw if the directory does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task> ReaddirAsync(string path, CancellationToken cancellationToken); + + /// Lists entries with type info in a directory. Throw if the directory does not exist. + /// SessionFs-relative path. + /// Cancellation token. + protected abstract Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken); + + /// Removes a file or directory. Throw if the path does not exist (unless is true). + /// SessionFs-relative path. + /// Whether to remove directory contents recursively. + /// If true, do not throw when the path does not exist. + /// Cancellation token. + protected abstract Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken); + + /// Renames/moves a file or directory. + /// Source path. + /// Destination path. + /// Cancellation token. + protected abstract Task RenameAsync(string src, string dest, CancellationToken cancellationToken); + + // ---- ISessionFsHandler implementation (private, handles error mapping) ---- + + async Task ISessionFsHandler.ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken) + { + try + { + var content = await ReadFileAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReadFileResult { Content = content }; + } + catch (Exception ex) + { + return new SessionFsReadFileResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken) + { + try + { + await WriteFileAsync(request.Path, request.Content, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken) + { + try + { + await AppendFileAsync(request.Path, request.Content, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken) + { + try + { + var exists = await ExistsAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsExistsResult { Exists = exists }; + } + catch + { + return new SessionFsExistsResult { Exists = false }; + } + } + + async Task ISessionFsHandler.StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken) + { + try + { + return await StatAsync(request.Path, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) + { + return new SessionFsStatResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken) + { + try + { + await MkdirAsync(request.Path, request.Recursive ?? false, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken) + { + try + { + var entries = await ReaddirAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReaddirResult { Entries = entries }; + } + catch (Exception ex) + { + return new SessionFsReaddirResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken) + { + try + { + var entries = await ReaddirWithTypesAsync(request.Path, cancellationToken).ConfigureAwait(false); + return new SessionFsReaddirWithTypesResult { Entries = entries }; + } + catch (Exception ex) + { + return new SessionFsReaddirWithTypesResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken) + { + try + { + await RmAsync(request.Path, request.Recursive ?? false, request.Force ?? false, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + async Task ISessionFsHandler.RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken) + { + try + { + await RenameAsync(request.Src, request.Dest, cancellationToken).ConfigureAwait(false); + return null; + } + catch (Exception ex) + { + return ToSessionFsError(ex); + } + } + + private static SessionFsError ToSessionFsError(Exception ex) + { + var code = ex is FileNotFoundException or DirectoryNotFoundException + ? SessionFsErrorCode.ENOENT + : SessionFsErrorCode.UNKNOWN; + return new SessionFsError { Code = code, Message = ex.Message }; + } +} diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index fd42d0c27..fe952c8c4 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1909,7 +1909,7 @@ protected SessionConfig(SessionConfig? other) /// Supplies a handler for session filesystem operations. /// This is used only when is configured. /// - public Func? CreateSessionFsHandler { get; set; } + public Func? CreateSessionFsHandler { get; set; } /// /// Creates a shallow clone of this instance. @@ -2148,7 +2148,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// Supplies a handler for session filesystem operations. /// This is used only when is configured. /// - public Func? CreateSessionFsHandler { get; set; } + public Func? CreateSessionFsHandler { get; set; } /// /// Creates a shallow clone of this instance. diff --git a/dotnet/test/SessionFsTests.cs b/dotnet/test/SessionFsTests.cs index e7bef3203..896a8f1fe 100644 --- a/dotnet/test/SessionFsTests.cs +++ b/dotnet/test/SessionFsTests.cs @@ -56,7 +56,7 @@ public async Task Should_Load_Session_Data_From_Fs_Provider_On_Resume() try { await using var client = CreateSessionFsClient(providerRoot); - Func createSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot); + Func createSessionFsHandler = s => new TestSessionFsHandler(s.SessionId, providerRoot); var session1 = await client.CreateSessionAsync(new SessionConfig { @@ -95,7 +95,7 @@ public async Task Should_Reject_SetProvider_When_Sessions_Already_Exist() try { await using var client1 = CreateSessionFsClient(providerRoot, useStdio: false); - var createSessionFsHandler = (Func)(s => new TestSessionFsHandler(s.SessionId, providerRoot)); + var createSessionFsHandler = (Func)(s => new TestSessionFsHandler(s.SessionId, providerRoot)); _ = await client1.CreateSessionAsync(new SessionConfig { @@ -431,212 +431,135 @@ private static string NormalizeRelativePathSegment(string segment, string paramN return normalized; } - private sealed class TestSessionFsHandler(string sessionId, string rootDir) : ISessionFsHandler + private sealed class TestSessionFsHandler(string sessionId, string rootDir) : SessionFsProvider { - public async Task ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken = default) + protected override async Task ReadFileAsync(string path, CancellationToken cancellationToken) { - try - { - var content = await File.ReadAllTextAsync(ResolvePath(request.Path), cancellationToken); - return new SessionFsReadFileResult { Content = content }; - } - catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) - { - return new SessionFsReadFileResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; - } - catch (Exception ex) - { - return new SessionFsReadFileResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; - } + return await File.ReadAllTextAsync(ResolvePath(path), cancellationToken); } - public async Task WriteFileAsync(SessionFsWriteFileRequest request, CancellationToken cancellationToken = default) + protected override async Task WriteFileAsync(string path, string content, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); - await File.WriteAllTextAsync(fullPath, request.Content, cancellationToken); - return null; + await File.WriteAllTextAsync(fullPath, content, cancellationToken); } - public async Task AppendFileAsync(SessionFsAppendFileRequest request, CancellationToken cancellationToken = default) + protected override async Task AppendFileAsync(string path, string content, CancellationToken cancellationToken) { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); - await File.AppendAllTextAsync(fullPath, request.Content, cancellationToken); - return null; + await File.AppendAllTextAsync(fullPath, content, cancellationToken); } - public async Task ExistsAsync(SessionFsExistsRequest request, CancellationToken cancellationToken = default) + protected override Task ExistsAsync(string path, CancellationToken cancellationToken) { - await Task.CompletedTask; - try - { - var fullPath = ResolvePath(request.Path); - return new SessionFsExistsResult - { - Exists = File.Exists(fullPath) || Directory.Exists(fullPath), - }; - } - catch - { - return new SessionFsExistsResult { Exists = false }; - } + var fullPath = ResolvePath(path); + return Task.FromResult(File.Exists(fullPath) || Directory.Exists(fullPath)); } - public async Task StatAsync(SessionFsStatRequest request, CancellationToken cancellationToken = default) + protected override Task StatAsync(string path, CancellationToken cancellationToken) { - await Task.CompletedTask; - try + var fullPath = ResolvePath(path); + if (File.Exists(fullPath)) { - var fullPath = ResolvePath(request.Path); - if (File.Exists(fullPath)) - { - var info = new FileInfo(fullPath); - return new SessionFsStatResult - { - IsFile = true, - IsDirectory = false, - Size = info.Length, - Mtime = info.LastWriteTimeUtc, - Birthtime = info.CreationTimeUtc, - }; - } - - var dirInfo = new DirectoryInfo(fullPath); - if (!dirInfo.Exists) + var info = new FileInfo(fullPath); + return Task.FromResult(new SessionFsStatResult { - return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = $"Path does not exist: {request.Path}" } }; - } - - return new SessionFsStatResult - { - IsFile = false, - IsDirectory = true, - Size = 0, - Mtime = dirInfo.LastWriteTimeUtc, - Birthtime = dirInfo.CreationTimeUtc, - }; + IsFile = true, + IsDirectory = false, + Size = info.Length, + Mtime = info.LastWriteTimeUtc, + Birthtime = info.CreationTimeUtc, + }); } - catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + + var dirInfo = new DirectoryInfo(fullPath); + if (!dirInfo.Exists) { - return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; + throw new DirectoryNotFoundException($"Path does not exist: {path}"); } - catch (Exception ex) + + return Task.FromResult(new SessionFsStatResult { - return new SessionFsStatResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; - } + IsFile = false, + IsDirectory = true, + Size = 0, + Mtime = dirInfo.LastWriteTimeUtc, + Birthtime = dirInfo.CreationTimeUtc, + }); } - public async Task MkdirAsync(SessionFsMkdirRequest request, CancellationToken cancellationToken = default) + protected override Task MkdirAsync(string path, bool recursive, CancellationToken cancellationToken) { - await Task.CompletedTask; - Directory.CreateDirectory(ResolvePath(request.Path)); - return null; + Directory.CreateDirectory(ResolvePath(path)); + return Task.CompletedTask; } - public async Task ReaddirAsync(SessionFsReaddirRequest request, CancellationToken cancellationToken = default) + protected override Task> ReaddirAsync(string path, CancellationToken cancellationToken) { - await Task.CompletedTask; - try - { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(Path.GetFileName) - .Where(name => name is not null) - .Cast() - .ToList(); - - return new SessionFsReaddirResult { Entries = entries }; - } - catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) - { - return new SessionFsReaddirResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; - } - catch (Exception ex) - { - return new SessionFsReaddirResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; - } + IList entries = Directory + .EnumerateFileSystemEntries(ResolvePath(path)) + .Select(Path.GetFileName) + .Where(name => name is not null) + .Cast() + .ToList(); + return Task.FromResult(entries); } - public async Task ReaddirWithTypesAsync(SessionFsReaddirWithTypesRequest request, CancellationToken cancellationToken = default) + protected override Task> ReaddirWithTypesAsync(string path, CancellationToken cancellationToken) { - await Task.CompletedTask; - try - { - var entries = Directory - .EnumerateFileSystemEntries(ResolvePath(request.Path)) - .Select(path => new SessionFsReaddirWithTypesEntry - { - Name = Path.GetFileName(path), - Type = Directory.Exists(path) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, - }) - .ToList(); - - return new SessionFsReaddirWithTypesResult { Entries = entries }; - } - catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) - { - return new SessionFsReaddirWithTypesResult { Error = new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message } }; - } - catch (Exception ex) - { - return new SessionFsReaddirWithTypesResult { Error = new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message } }; - } + IList entries = Directory + .EnumerateFileSystemEntries(ResolvePath(path)) + .Select(p => new SessionFsReaddirWithTypesEntry + { + Name = Path.GetFileName(p), + Type = Directory.Exists(p) ? SessionFsReaddirWithTypesEntryType.Directory : SessionFsReaddirWithTypesEntryType.File, + }) + .ToList(); + return Task.FromResult(entries); } - public async Task RmAsync(SessionFsRmRequest request, CancellationToken cancellationToken = default) + protected override Task RmAsync(string path, bool recursive, bool force, CancellationToken cancellationToken) { - await Task.CompletedTask; - try - { - var fullPath = ResolvePath(request.Path); + var fullPath = ResolvePath(path); - if (File.Exists(fullPath)) - { - File.Delete(fullPath); - return null; - } - - if (Directory.Exists(fullPath)) - { - Directory.Delete(fullPath, request.Recursive ?? false); - return null; - } - - if (request.Force == true) - { - return null; - } - - return new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = $"Path does not exist: {request.Path}" }; + if (File.Exists(fullPath)) + { + File.Delete(fullPath); + return Task.CompletedTask; } - catch (Exception ex) when (ex is FileNotFoundException or DirectoryNotFoundException) + + if (Directory.Exists(fullPath)) { - return new SessionFsError { Code = SessionFsErrorCode.ENOENT, Message = ex.Message }; + Directory.Delete(fullPath, recursive); + return Task.CompletedTask; } - catch (Exception ex) + + if (force) { - return new SessionFsError { Code = SessionFsErrorCode.UNKNOWN, Message = ex.Message }; + return Task.CompletedTask; } + + throw new FileNotFoundException($"Path does not exist: {path}"); } - public async Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default) + protected override Task RenameAsync(string src, string dest, CancellationToken cancellationToken) { - await Task.CompletedTask; - var src = ResolvePath(request.Src); - var dest = ResolvePath(request.Dest); - Directory.CreateDirectory(Path.GetDirectoryName(dest)!); + var srcPath = ResolvePath(src); + var destPath = ResolvePath(dest); + Directory.CreateDirectory(Path.GetDirectoryName(destPath)!); - if (Directory.Exists(src)) + if (Directory.Exists(srcPath)) { - Directory.Move(src, dest); + Directory.Move(srcPath, destPath); } else { - File.Move(src, dest, overwrite: true); + File.Move(srcPath, destPath, overwrite: true); } - return null; + return Task.CompletedTask; } private string ResolvePath(string sessionPath) diff --git a/go/client.go b/go/client.go index 37e572dc8..feb31eeb8 100644 --- a/go/client.go +++ b/go/client.go @@ -675,7 +675,7 @@ func (c *Client) CreateSession(ctx context.Context, config *SessionConfig) (*Ses c.sessionsMux.Unlock() return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options") } - session.clientSessionApis.SessionFs = config.CreateSessionFsHandler(session) + session.clientSessionApis.SessionFs = newSessionFsAdapter(config.CreateSessionFsHandler(session)) } result, err := c.client.Request("session.create", req) @@ -833,7 +833,7 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, c.sessionsMux.Unlock() return nil, fmt.Errorf("CreateSessionFsHandler is required in session config when SessionFs is enabled in client options") } - session.clientSessionApis.SessionFs = config.CreateSessionFsHandler(session) + session.clientSessionApis.SessionFs = newSessionFsAdapter(config.CreateSessionFsHandler(session)) } result, err := c.client.Request("session.resume", req) diff --git a/go/internal/e2e/session_fs_test.go b/go/internal/e2e/session_fs_test.go index 5f4650a58..3e9726c62 100644 --- a/go/internal/e2e/session_fs_test.go +++ b/go/internal/e2e/session_fs_test.go @@ -17,7 +17,7 @@ import ( func TestSessionFs(t *testing.T) { ctx := testharness.NewTestContext(t) providerRoot := t.TempDir() - createSessionFsHandler := func(session *copilot.Session) rpc.SessionFsHandler { + createSessionFsHandler := func(session *copilot.Session) copilot.SessionFsProvider { return &testSessionFsHandler{ root: providerRoot, sessionID: session.SessionID, @@ -342,65 +342,54 @@ type testSessionFsHandler struct { sessionID string } -func (h *testSessionFsHandler) ReadFile(request *rpc.SessionFSReadFileRequest) (*rpc.SessionFSReadFileResult, error) { - content, err := os.ReadFile(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) ReadFile(path string) (string, error) { + content, err := os.ReadFile(providerPath(h.root, h.sessionID, path)) if err != nil { - return nil, err + return "", err } - return &rpc.SessionFSReadFileResult{Content: string(content)}, nil + return string(content), nil } -func (h *testSessionFsHandler) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSError, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) WriteFile(path string, content string) error { + fullPath := providerPath(h.root, h.sessionID, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + return err } - mode := os.FileMode(0o666) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) - } - return nil, os.WriteFile(path, []byte(request.Content), mode) + return os.WriteFile(fullPath, []byte(content), 0o666) } -func (h *testSessionFsHandler) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSError, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) AppendFile(path string, content string) error { + fullPath := providerPath(h.root, h.sessionID, path) + if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil { + return err } - mode := os.FileMode(0o666) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) - } - f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, mode) + f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o666) if err != nil { - return nil, err + return err } defer f.Close() - _, err = f.WriteString(request.Content) - if err != nil { - return nil, err - } - return nil, nil + _, err = f.WriteString(content) + return err } -func (h *testSessionFsHandler) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { - _, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Exists(path string) (bool, error) { + _, err := os.Stat(providerPath(h.root, h.sessionID, path)) if err == nil { - return &rpc.SessionFSExistsResult{Exists: true}, nil + return true, nil } if os.IsNotExist(err) { - return &rpc.SessionFSExistsResult{Exists: false}, nil + return false, nil } - return nil, err + return false, err } -func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.SessionFSStatResult, error) { - info, err := os.Stat(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Stat(path string) (*copilot.SessionFsFileInfo, error) { + info, err := os.Stat(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } ts := info.ModTime().UTC() - return &rpc.SessionFSStatResult{ + return &copilot.SessionFsFileInfo{ IsFile: !info.IsDir(), IsDirectory: info.IsDir(), Size: info.Size(), @@ -409,20 +398,16 @@ func (h *testSessionFsHandler) Stat(request *rpc.SessionFSStatRequest) (*rpc.Ses }, nil } -func (h *testSessionFsHandler) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSError, error) { - path := providerPath(h.root, h.sessionID, request.Path) - mode := os.FileMode(0o777) - if request.Mode != nil { - mode = os.FileMode(uint32(*request.Mode)) - } - if request.Recursive != nil && *request.Recursive { - return nil, os.MkdirAll(path, mode) +func (h *testSessionFsHandler) Mkdir(path string, recursive bool) error { + fullPath := providerPath(h.root, h.sessionID, path) + if recursive { + return os.MkdirAll(fullPath, 0o777) } - return nil, os.Mkdir(path, mode) + return os.Mkdir(fullPath, 0o777) } -func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { - entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) Readdir(path string) ([]string, error) { + entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } @@ -430,11 +415,11 @@ func (h *testSessionFsHandler) Readdir(request *rpc.SessionFSReaddirRequest) (*r for _, entry := range entries { names = append(names, entry.Name()) } - return &rpc.SessionFSReaddirResult{Entries: names}, nil + return names, nil } -func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesRequest) (*rpc.SessionFSReaddirWithTypesResult, error) { - entries, err := os.ReadDir(providerPath(h.root, h.sessionID, request.Path)) +func (h *testSessionFsHandler) ReaddirWithTypes(path string) ([]rpc.SessionFSReaddirWithTypesEntry, error) { + entries, err := os.ReadDir(providerPath(h.root, h.sessionID, path)) if err != nil { return nil, err } @@ -449,34 +434,29 @@ func (h *testSessionFsHandler) ReaddirWithTypes(request *rpc.SessionFSReaddirWit Type: entryType, }) } - return &rpc.SessionFSReaddirWithTypesResult{Entries: result}, nil + return result, nil } -func (h *testSessionFsHandler) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSError, error) { - path := providerPath(h.root, h.sessionID, request.Path) - if request.Recursive != nil && *request.Recursive { - err := os.RemoveAll(path) - if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return nil, nil - } - return nil, err +func (h *testSessionFsHandler) Rm(path string, recursive bool, force bool) error { + fullPath := providerPath(h.root, h.sessionID, path) + var err error + if recursive { + err = os.RemoveAll(fullPath) + } else { + err = os.Remove(fullPath) } - err := os.Remove(path) - if err != nil && request.Force != nil && *request.Force && os.IsNotExist(err) { - return nil, nil + if err != nil && force && os.IsNotExist(err) { + return nil } - return nil, err + return err } -func (h *testSessionFsHandler) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSError, error) { - dest := providerPath(h.root, h.sessionID, request.Dest) - if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil { - return nil, err +func (h *testSessionFsHandler) Rename(src string, dest string) error { + destPath := providerPath(h.root, h.sessionID, dest) + if err := os.MkdirAll(filepath.Dir(destPath), 0o755); err != nil { + return err } - return nil, os.Rename( - providerPath(h.root, h.sessionID, request.Src), - dest, - ) + return os.Rename(providerPath(h.root, h.sessionID, src), destPath) } func providerPath(root string, sessionID string, path string) string { diff --git a/go/session_fs_provider.go b/go/session_fs_provider.go new file mode 100644 index 000000000..db5259d6d --- /dev/null +++ b/go/session_fs_provider.go @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +package copilot + +import ( + "errors" + "os" + "time" + + "github.com/github/copilot-sdk/go/rpc" +) + +// SessionFsProvider is the interface that SDK users implement to provide +// a session filesystem. Methods use idiomatic Go error handling: return an +// error for failures (the adapter maps os.ErrNotExist → ENOENT automatically). +type SessionFsProvider interface { + // ReadFile reads the full content of a file. Return os.ErrNotExist (or wrap it) + // if the file does not exist. + ReadFile(path string) (string, error) + // WriteFile writes content to a file, creating it and parent directories if needed. + WriteFile(path string, content string) error + // AppendFile appends content to a file, creating it and parent directories if needed. + AppendFile(path string, content string) error + // Exists checks whether the given path exists. + Exists(path string) (bool, error) + // Stat returns metadata about a file or directory. + // Return os.ErrNotExist if the path does not exist. + Stat(path string) (*SessionFsFileInfo, error) + // Mkdir creates a directory. If recursive is true, create parent directories as needed. + Mkdir(path string, recursive bool) error + // Readdir lists the names of entries in a directory. + // Return os.ErrNotExist if the directory does not exist. + Readdir(path string) ([]string, error) + // ReaddirWithTypes lists entries with type information. + // Return os.ErrNotExist if the directory does not exist. + ReaddirWithTypes(path string) ([]rpc.SessionFSReaddirWithTypesEntry, error) + // Rm removes a file or directory. If recursive is true, remove contents too. + // If force is true, do not return an error when the path does not exist. + Rm(path string, recursive bool, force bool) error + // Rename moves/renames a file or directory. + Rename(src string, dest string) error +} + +// SessionFsFileInfo holds file metadata returned by SessionFsProvider.Stat. +type SessionFsFileInfo struct { + IsFile bool + IsDirectory bool + Size int64 + Mtime time.Time + Birthtime time.Time +} + +// sessionFsAdapter wraps a SessionFsProvider to implement rpc.SessionFsHandler, +// converting idiomatic Go errors into SessionFSError results. +type sessionFsAdapter struct { + provider SessionFsProvider +} + +func newSessionFsAdapter(provider SessionFsProvider) rpc.SessionFsHandler { + return &sessionFsAdapter{provider: provider} +} + +func (a *sessionFsAdapter) ReadFile(request *rpc.SessionFSReadFileRequest) (*rpc.SessionFSReadFileResult, error) { + content, err := a.provider.ReadFile(request.Path) + if err != nil { + return &rpc.SessionFSReadFileResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReadFileResult{Content: content}, nil +} + +func (a *sessionFsAdapter) WriteFile(request *rpc.SessionFSWriteFileRequest) (*rpc.SessionFSError, error) { + if err := a.provider.WriteFile(request.Path, request.Content); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) AppendFile(request *rpc.SessionFSAppendFileRequest) (*rpc.SessionFSError, error) { + if err := a.provider.AppendFile(request.Path, request.Content); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Exists(request *rpc.SessionFSExistsRequest) (*rpc.SessionFSExistsResult, error) { + exists, err := a.provider.Exists(request.Path) + if err != nil { + return &rpc.SessionFSExistsResult{Exists: false}, nil + } + return &rpc.SessionFSExistsResult{Exists: exists}, nil +} + +func (a *sessionFsAdapter) Stat(request *rpc.SessionFSStatRequest) (*rpc.SessionFSStatResult, error) { + info, err := a.provider.Stat(request.Path) + if err != nil { + return &rpc.SessionFSStatResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSStatResult{ + IsFile: info.IsFile, + IsDirectory: info.IsDirectory, + Size: info.Size, + Mtime: info.Mtime, + Birthtime: info.Birthtime, + }, nil +} + +func (a *sessionFsAdapter) Mkdir(request *rpc.SessionFSMkdirRequest) (*rpc.SessionFSError, error) { + recursive := request.Recursive != nil && *request.Recursive + if err := a.provider.Mkdir(request.Path, recursive); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Readdir(request *rpc.SessionFSReaddirRequest) (*rpc.SessionFSReaddirResult, error) { + entries, err := a.provider.Readdir(request.Path) + if err != nil { + return &rpc.SessionFSReaddirResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReaddirResult{Entries: entries}, nil +} + +func (a *sessionFsAdapter) ReaddirWithTypes(request *rpc.SessionFSReaddirWithTypesRequest) (*rpc.SessionFSReaddirWithTypesResult, error) { + entries, err := a.provider.ReaddirWithTypes(request.Path) + if err != nil { + return &rpc.SessionFSReaddirWithTypesResult{Error: toSessionFsError(err)}, nil + } + return &rpc.SessionFSReaddirWithTypesResult{Entries: entries}, nil +} + +func (a *sessionFsAdapter) Rm(request *rpc.SessionFSRmRequest) (*rpc.SessionFSError, error) { + recursive := request.Recursive != nil && *request.Recursive + force := request.Force != nil && *request.Force + if err := a.provider.Rm(request.Path, recursive, force); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func (a *sessionFsAdapter) Rename(request *rpc.SessionFSRenameRequest) (*rpc.SessionFSError, error) { + if err := a.provider.Rename(request.Src, request.Dest); err != nil { + return toSessionFsError(err), nil + } + return nil, nil +} + +func toSessionFsError(err error) *rpc.SessionFSError { + code := rpc.SessionFSErrorCodeUNKNOWN + if errors.Is(err, os.ErrNotExist) { + code = rpc.SessionFSErrorCodeENOENT + } + msg := err.Error() + return &rpc.SessionFSError{Code: code, Message: &msg} +} diff --git a/go/types.go b/go/types.go index 4ef19c95b..e3e21467e 100644 --- a/go/types.go +++ b/go/types.go @@ -561,7 +561,7 @@ type SessionConfig struct { OnEvent SessionEventHandler // CreateSessionFsHandler supplies a handler for session filesystem operations. // This takes effect only when ClientOptions.SessionFs is configured. - CreateSessionFsHandler func(session *Session) rpc.SessionFsHandler + CreateSessionFsHandler func(session *Session) SessionFsProvider // Commands registers slash-commands for this session. Each command appears as // /name in the CLI TUI for the user to invoke. The Handler is called when the // command is executed. @@ -775,7 +775,7 @@ type ResumeSessionConfig struct { OnEvent SessionEventHandler // CreateSessionFsHandler supplies a handler for session filesystem operations. // This takes effect only when ClientOptions.SessionFs is configured. - CreateSessionFsHandler func(session *Session) rpc.SessionFsHandler + CreateSessionFsHandler func(session *Session) SessionFsProvider // Commands registers slash-commands for this session. See SessionConfig.Commands. Commands []CommandDefinition // OnElicitationRequest is a handler for elicitation requests from the server. diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index c8c137c3d..62658de4d 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -27,6 +27,7 @@ import { import { createServerRpc, registerClientSessionApiHandlers } from "./generated/rpc.js"; import { getSdkProtocolVersion } from "./sdkProtocolVersion.js"; import { CopilotSession, NO_RESULT_PERMISSION_V2_ERROR } from "./session.js"; +import { createSessionFsAdapter } from "./sessionFsProvider.js"; import { getTraceContext } from "./telemetry.js"; import type { ConnectionState, @@ -711,7 +712,7 @@ export class CopilotClient { this.sessions.set(sessionId, session); if (this.sessionFsConfig) { if (config.createSessionFsHandler) { - session.clientSessionApis.sessionFs = config.createSessionFsHandler(session); + session.clientSessionApis.sessionFs = createSessionFsAdapter(config.createSessionFsHandler(session)); } else { throw new Error( "createSessionFsHandler is required in session config when sessionFs is enabled in client options." @@ -849,7 +850,7 @@ export class CopilotClient { this.sessions.set(sessionId, session); if (this.sessionFsConfig) { if (config.createSessionFsHandler) { - session.clientSessionApis.sessionFs = config.createSessionFsHandler(session); + session.clientSessionApis.sessionFs = createSessionFsAdapter(config.createSessionFsHandler(session)); } else { throw new Error( "createSessionFsHandler is required in session config when sessionFs is enabled in client options." diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index e2942998a..636fa82a4 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -66,7 +66,9 @@ export type { SessionMetadata, SessionUiApi, SessionFsConfig, - SessionFsHandler, + SessionFsProvider, + SessionFsFileInfo, + createSessionFsAdapter, SystemMessageAppendConfig, SystemMessageConfig, SystemMessageCustomizeConfig, diff --git a/nodejs/src/sessionFsProvider.ts b/nodejs/src/sessionFsProvider.ts new file mode 100644 index 000000000..1b639810a --- /dev/null +++ b/nodejs/src/sessionFsProvider.ts @@ -0,0 +1,159 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------------------------------------------*/ + +import type { + SessionFsHandler, + SessionFsError, + SessionFsStatResult, + SessionFsReaddirWithTypesEntry, +} from "./generated/rpc.js"; + +/** + * File metadata returned by {@link SessionFsProvider.stat}. + * Same shape as the generated {@link SessionFsStatResult} but without the + * `error` field, since providers signal errors by throwing. + */ +export type SessionFsFileInfo = Omit; + +/** + * Interface for session filesystem providers. Implementors use idiomatic + * TypeScript patterns: throw on error, return values directly. Use + * {@link createSessionFsAdapter} to convert a provider into the + * {@link SessionFsHandler} expected by the SDK. + * + * Errors with a `code` property of `"ENOENT"` are mapped to the ENOENT + * error code; all others map to UNKNOWN. + */ +export interface SessionFsProvider { + /** Reads the full content of a file. Throw if the file does not exist. */ + readFile(path: string): Promise; + + /** Writes content to a file, creating parent directories if needed. */ + writeFile(path: string, content: string): Promise; + + /** Appends content to a file, creating parent directories if needed. */ + appendFile(path: string, content: string): Promise; + + /** Checks whether a path exists. */ + exists(path: string): Promise; + + /** Gets metadata about a file or directory. Throw if it does not exist. */ + stat(path: string): Promise; + + /** Creates a directory. If recursive is true, creates parents as needed. */ + mkdir(path: string, recursive: boolean, mode?: number): Promise; + + /** Lists entry names in a directory. Throw if it does not exist. */ + readdir(path: string): Promise; + + /** Lists entries with type info. Throw if the directory does not exist. */ + readdirWithTypes(path: string): Promise; + + /** Removes a file or directory. If force is true, do not throw on ENOENT. */ + rm(path: string, recursive: boolean, force: boolean): Promise; + + /** Renames/moves a file or directory. */ + rename(src: string, dest: string): Promise; +} + +/** + * Wraps a {@link SessionFsProvider} into the {@link SessionFsHandler} + * interface expected by the SDK, converting thrown errors into + * {@link SessionFsError} results. + */ +export function createSessionFsAdapter(provider: SessionFsProvider): SessionFsHandler { + return { + readFile: async ({ path }) => { + try { + const content = await provider.readFile(path); + return { content }; + } catch (err) { + return { content: "", error: toSessionFsError(err) }; + } + }, + writeFile: async ({ path, content }) => { + try { + await provider.writeFile(path, content); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + appendFile: async ({ path, content }) => { + try { + await provider.appendFile(path, content); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + exists: async ({ path }) => { + try { + return { exists: await provider.exists(path) }; + } catch { + return { exists: false }; + } + }, + stat: async ({ path }) => { + try { + return await provider.stat(path); + } catch (err) { + return { + isFile: false, + isDirectory: false, + size: 0, + mtime: new Date().toISOString(), + birthtime: new Date().toISOString(), + error: toSessionFsError(err), + }; + } + }, + mkdir: async ({ path, recursive, mode }) => { + try { + await provider.mkdir(path, recursive ?? false, mode); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + readdir: async ({ path }) => { + try { + const entries = await provider.readdir(path); + return { entries }; + } catch (err) { + return { entries: [], error: toSessionFsError(err) }; + } + }, + readdirWithTypes: async ({ path }) => { + try { + const entries = await provider.readdirWithTypes(path); + return { entries }; + } catch (err) { + return { entries: [], error: toSessionFsError(err) }; + } + }, + rm: async ({ path, recursive, force }) => { + try { + await provider.rm(path, recursive ?? false, force ?? false); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + rename: async ({ src, dest }) => { + try { + await provider.rename(src, dest); + return undefined; + } catch (err) { + return toSessionFsError(err); + } + }, + }; +} + +function toSessionFsError(err: unknown): SessionFsError { + const e = err as NodeJS.ErrnoException; + const code = e.code === "ENOENT" ? "ENOENT" : "UNKNOWN"; + return { code, message: e.message ?? String(err) }; +} diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 351f95136..fb46635ae 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -7,11 +7,13 @@ */ // Import and re-export generated session event types -import type { SessionFsHandler } from "./generated/rpc.js"; +import type { SessionFsProvider } from "./sessionFsProvider.js"; import type { SessionEvent as GeneratedSessionEvent } from "./generated/session-events.js"; import type { CopilotSession } from "./session.js"; export type SessionEvent = GeneratedSessionEvent; -export type { SessionFsHandler } from "./generated/rpc.js"; +export type { SessionFsProvider } from "./sessionFsProvider.js"; +export { createSessionFsAdapter } from "./sessionFsProvider.js"; +export type { SessionFsFileInfo } from "./sessionFsProvider.js"; /** * Options for creating a CopilotClient @@ -1330,7 +1332,7 @@ export interface SessionConfig { * Supplies a handler for session filesystem operations. This takes effect * only if {@link CopilotClientOptions.sessionFs} is configured. */ - createSessionFsHandler?: (session: CopilotSession) => SessionFsHandler; + createSessionFsHandler?: (session: CopilotSession) => SessionFsProvider; } /** diff --git a/nodejs/test/e2e/session_fs.test.ts b/nodejs/test/e2e/session_fs.test.ts index 5d6ad26da..c52e92dfa 100644 --- a/nodejs/test/e2e/session_fs.test.ts +++ b/nodejs/test/e2e/session_fs.test.ts @@ -6,13 +6,15 @@ import { SessionCompactionCompleteEvent } from "@github/copilot/sdk"; import { MemoryProvider, VirtualProvider } from "@platformatic/vfs"; import { describe, expect, it, onTestFinished } from "vitest"; import { CopilotClient } from "../../src/client.js"; -import { SessionFsHandler } from "../../src/generated/rpc.js"; +import type { SessionFsReaddirWithTypesEntry } from "../../src/generated/rpc.js"; import { approveAll, CopilotSession, defineTool, SessionEvent, type SessionFsConfig, + type SessionFsProvider, + type SessionFsFileInfo, } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; @@ -217,109 +219,56 @@ const sessionFsConfig: SessionFsConfig = { function createTestSessionFsHandler( session: CopilotSession, provider: VirtualProvider -): SessionFsHandler { - const sp = (sessionId: string, path: string) => - `/${sessionId}${path.startsWith("/") ? path : "/" + path}`; - - function mapError(err: unknown): { code: "ENOENT" | "UNKNOWN"; message?: string } { - const e = err as NodeJS.ErrnoException; - if (e.code === "ENOENT") return { code: "ENOENT", message: e.message }; - return { code: "UNKNOWN", message: e.message ?? String(err) }; - } +): SessionFsProvider { + const sp = (path: string) => + `/${session.sessionId}${path.startsWith("/") ? path : "/" + path}`; return { - readFile: async ({ path }) => { - try { - const content = await provider.readFile(sp(session.sessionId, path), "utf8"); - return { content: content as string }; - } catch (err) { - return { content: "", error: mapError(err) }; - } + async readFile(path: string): Promise { + return (await provider.readFile(sp(path), "utf8")) as string; }, - writeFile: async ({ path, content }) => { - try { - await provider.writeFile(sp(session.sessionId, path), content); - return undefined; - } catch (err) { - return mapError(err); - } + async writeFile(path: string, content: string): Promise { + await provider.writeFile(sp(path), content); }, - appendFile: async ({ path, content }) => { - try { - await provider.appendFile(sp(session.sessionId, path), content); - return undefined; - } catch (err) { - return mapError(err); - } + async appendFile(path: string, content: string): Promise { + await provider.appendFile(sp(path), content); }, - exists: async ({ path }) => { - return { exists: await provider.exists(sp(session.sessionId, path)) }; + async exists(path: string): Promise { + return provider.exists(sp(path)); }, - stat: async ({ path }) => { - try { - const st = await provider.stat(sp(session.sessionId, path)); - return { - isFile: st.isFile(), - isDirectory: st.isDirectory(), - size: st.size, - mtime: new Date(st.mtimeMs).toISOString(), - birthtime: new Date(st.birthtimeMs).toISOString(), - }; - } catch (err) { - return { isFile: false, isDirectory: false, size: 0, mtime: new Date().toISOString(), birthtime: new Date().toISOString(), error: mapError(err) }; - } + async stat(path: string): Promise { + const st = await provider.stat(sp(path)); + return { + isFile: st.isFile(), + isDirectory: st.isDirectory(), + size: st.size, + mtime: new Date(st.mtimeMs).toISOString(), + birthtime: new Date(st.birthtimeMs).toISOString(), + }; }, - mkdir: async ({ path, recursive, mode }) => { - try { - await provider.mkdir(sp(session.sessionId, path), { - recursive: recursive ?? false, - mode, - }); - return undefined; - } catch (err) { - return mapError(err); - } + async mkdir(path: string, recursive: boolean, mode?: number): Promise { + await provider.mkdir(sp(path), { recursive, mode }); }, - readdir: async ({ path }) => { - try { - const entries = await provider.readdir(sp(session.sessionId, path)); - return { entries: entries as string[] }; - } catch (err) { - return { entries: [], error: mapError(err) }; - } + async readdir(path: string): Promise { + return (await provider.readdir(sp(path))) as string[]; }, - readdirWithTypes: async ({ path }) => { - try { - const names = (await provider.readdir(sp(session.sessionId, path))) as string[]; - const entries = await Promise.all( - names.map(async (name) => { - const st = await provider.stat(sp(session.sessionId, `${path}/${name}`)); - return { - name, - type: st.isDirectory() ? ("directory" as const) : ("file" as const), - }; - }) - ); - return { entries }; - } catch (err) { - return { entries: [], error: mapError(err) }; - } + async readdirWithTypes(path: string): Promise { + const names = (await provider.readdir(sp(path))) as string[]; + return Promise.all( + names.map(async (name) => { + const st = await provider.stat(sp(`${path}/${name}`)); + return { + name, + type: st.isDirectory() ? ("directory" as const) : ("file" as const), + }; + }) + ); }, - rm: async ({ path }) => { - try { - await provider.unlink(sp(session.sessionId, path)); - return undefined; - } catch (err) { - return mapError(err); - } + async rm(path: string): Promise { + await provider.unlink(sp(path)); }, - rename: async ({ src, dest }) => { - try { - await provider.rename(sp(session.sessionId, src), sp(session.sessionId, dest)); - return undefined; - } catch (err) { - return mapError(err); - } + async rename(src: string, dest: string): Promise { + await provider.rename(sp(src), sp(dest)); }, }; } diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index 190c058a0..ad9e28803 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -26,10 +26,14 @@ ProviderConfig, SessionCapabilities, SessionFsConfig, - SessionFsHandler, SessionUiApi, SessionUiCapabilities, ) +from .session_fs_provider import ( + SessionFsFileInfo, + SessionFsProvider, + create_session_fs_adapter, +) from .tools import convert_mcp_call_tool_result, define_tool __version__ = "0.1.0" @@ -53,7 +57,9 @@ "ProviderConfig", "SessionCapabilities", "SessionFsConfig", - "SessionFsHandler", + "SessionFsFileInfo", + "SessionFsProvider", + "create_session_fs_adapter", "SessionUiApi", "SessionUiCapabilities", "SubprocessConfig", diff --git a/python/copilot/client.py b/python/copilot/client.py index 4c1186f23..cc05bab61 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -60,6 +60,7 @@ _PermissionHandlerFn, ) from .tools import Tool, ToolInvocation, ToolResult +from .session_fs_provider import create_session_fs_adapter # ============================================================================ # Connection Types @@ -1427,7 +1428,7 @@ async def create_session( "create_session_fs_handler is required in session config when " "session_fs is enabled in client options." ) - session._client_session_apis.session_fs = create_session_fs_handler(session) + session._client_session_apis.session_fs = create_session_fs_adapter(create_session_fs_handler(session)) session._register_tools(tools) session._register_commands(commands) session._register_permission_handler(on_permission_request) @@ -1682,7 +1683,7 @@ async def resume_session( "create_session_fs_handler is required in session config when " "session_fs is enabled in client options." ) - session._client_session_apis.session_fs = create_session_fs_handler(session) + session._client_session_apis.session_fs = create_session_fs_adapter(create_session_fs_handler(session)) session._register_tools(tools) session._register_commands(commands) session._register_permission_handler(on_permission_request) diff --git a/python/copilot/session.py b/python/copilot/session.py index 6a0d8844b..b429ccc50 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -30,7 +30,6 @@ PermissionDecision, PermissionDecisionRequest, UIElicitationSchemaType, - SessionFsHandler, SessionLogLevel, SessionRpc, ToolCallResult, @@ -58,6 +57,7 @@ session_event_from_dict, ) from .tools import Tool, ToolHandler, ToolInvocation, ToolResult +from .session_fs_provider import SessionFsProvider if TYPE_CHECKING: from .client import ModelCapabilitiesOverride @@ -410,7 +410,7 @@ class ElicitationContext(TypedDict, total=False): ] """Handler invoked when the server dispatches an elicitation request to this client.""" -CreateSessionFsHandler = Callable[["CopilotSession"], SessionFsHandler] +CreateSessionFsHandler = Callable[["CopilotSession"], "SessionFsProvider"] # ============================================================================ diff --git a/python/copilot/session_fs_provider.py b/python/copilot/session_fs_provider.py new file mode 100644 index 000000000..ceb5ffd17 --- /dev/null +++ b/python/copilot/session_fs_provider.py @@ -0,0 +1,231 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# -------------------------------------------------------------------------------------------- + +"""Idiomatic base class for session filesystem providers. + +Subclasses override the abstract methods using standard Python patterns: +raise on error, return values directly. The :func:`create_session_fs_adapter` +function wraps a provider into the generated :class:`SessionFsHandler` +protocol expected by the SDK, converting exceptions into +:class:`SessionFSError` results. + +Errors whose ``errno`` matches :data:`errno.ENOENT` are mapped to the +``ENOENT`` error code; all others map to ``UNKNOWN``. +""" + +from __future__ import annotations + +import abc +import errno +from dataclasses import dataclass +from datetime import datetime +from typing import Sequence + +from .generated.rpc import ( + SessionFSError, + SessionFSErrorCode, + SessionFSExistsResult, + SessionFSReaddirResult, + SessionFSReaddirWithTypesEntry, + SessionFSReaddirWithTypesResult, + SessionFSReadFileResult, + SessionFSStatResult, + SessionFsHandler, +) + + +@dataclass +class SessionFsFileInfo: + """File metadata returned by :meth:`SessionFsProvider.stat`.""" + + is_file: bool + is_directory: bool + size: int + mtime: datetime + birthtime: datetime + + +class SessionFsProvider(abc.ABC): + """Abstract base class for session filesystem providers. + + Subclasses implement the abstract methods below using idiomatic Python: + raise exceptions on errors and return values directly. Use + :func:`create_session_fs_adapter` to wrap a provider into the RPC + handler protocol. + """ + + @abc.abstractmethod + async def read_file(self, path: str) -> str: + """Read the full content of a file. Raise if the file does not exist.""" + + @abc.abstractmethod + async def write_file(self, path: str, content: str) -> None: + """Write *content* to a file, creating parent directories if needed.""" + + @abc.abstractmethod + async def append_file(self, path: str, content: str) -> None: + """Append *content* to a file, creating parent directories if needed.""" + + @abc.abstractmethod + async def exists(self, path: str) -> bool: + """Return whether *path* exists.""" + + @abc.abstractmethod + async def stat(self, path: str) -> SessionFsFileInfo: + """Return metadata for *path*. Raise if it does not exist.""" + + @abc.abstractmethod + async def mkdir(self, path: str, recursive: bool, mode: int | None = None) -> None: + """Create a directory. If *recursive* is ``True``, create parents.""" + + @abc.abstractmethod + async def readdir(self, path: str) -> list[str]: + """List entry names in a directory. Raise if it does not exist.""" + + @abc.abstractmethod + async def readdir_with_types( + self, path: str + ) -> Sequence[SessionFSReaddirWithTypesEntry]: + """List entries with type info. Raise if the directory does not exist.""" + + @abc.abstractmethod + async def rm(self, path: str, recursive: bool, force: bool) -> None: + """Remove a file or directory.""" + + @abc.abstractmethod + async def rename(self, src: str, dest: str) -> None: + """Rename / move a file or directory.""" + + +def create_session_fs_adapter(provider: SessionFsProvider) -> SessionFsHandler: + """Wrap a :class:`SessionFsProvider` into a :class:`SessionFsHandler`. + + The adapter catches exceptions thrown by the provider and converts them + into :class:`SessionFSError` results expected by the runtime. + """ + return _SessionFsAdapter(provider) + + +class _SessionFsAdapter: + """Internal adapter that bridges SessionFsProvider → SessionFsHandler.""" + + def __init__(self, provider: SessionFsProvider) -> None: + self._p = provider + + async def read_file(self, params: object) -> SessionFSReadFileResult: + try: + content = await self._p.read_file(params.path) # type: ignore[attr-defined] + return SessionFSReadFileResult.from_dict({"content": content}) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReadFileResult.from_dict( + {"content": "", "error": err.to_dict()} + ) + + async def write_file(self, params: object) -> SessionFSError | None: + try: + await self._p.write_file(params.path, params.content) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def append_file(self, params: object) -> SessionFSError | None: + try: + await self._p.append_file(params.path, params.content) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def exists(self, params: object) -> SessionFSExistsResult: + try: + result = await self._p.exists(params.path) # type: ignore[attr-defined] + return SessionFSExistsResult.from_dict({"exists": result}) + except Exception: + return SessionFSExistsResult.from_dict({"exists": False}) + + async def stat(self, params: object) -> SessionFSStatResult: + try: + info = await self._p.stat(params.path) # type: ignore[attr-defined] + return SessionFSStatResult( + is_file=info.is_file, + is_directory=info.is_directory, + size=info.size, + mtime=info.mtime, + birthtime=info.birthtime, + ) + except Exception as exc: + now = datetime.now() + err = _to_session_fs_error(exc) + return SessionFSStatResult( + is_file=False, + is_directory=False, + size=0, + mtime=now, + birthtime=now, + error=err, + ) + + async def mkdir(self, params: object) -> SessionFSError | None: + try: + await self._p.mkdir( + params.path, # type: ignore[attr-defined] + getattr(params, "recursive", False), + getattr(params, "mode", None), + ) + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def readdir(self, params: object) -> SessionFSReaddirResult: + try: + entries = await self._p.readdir(params.path) # type: ignore[attr-defined] + return SessionFSReaddirResult.from_dict({"entries": entries}) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReaddirResult.from_dict( + {"entries": [], "error": err.to_dict()} + ) + + async def readdir_with_types( + self, params: object + ) -> SessionFSReaddirWithTypesResult: + try: + entries = await self._p.readdir_with_types(params.path) # type: ignore[attr-defined] + return SessionFSReaddirWithTypesResult(entries=list(entries)) + except Exception as exc: + err = _to_session_fs_error(exc) + return SessionFSReaddirWithTypesResult.from_dict( + {"entries": [], "error": err.to_dict()} + ) + + async def rm(self, params: object) -> SessionFSError | None: + try: + await self._p.rm( + params.path, # type: ignore[attr-defined] + getattr(params, "recursive", False), + getattr(params, "force", False), + ) + return None + except Exception as exc: + return _to_session_fs_error(exc) + + async def rename(self, params: object) -> SessionFSError | None: + try: + await self._p.rename(params.src, params.dest) # type: ignore[attr-defined] + return None + except Exception as exc: + return _to_session_fs_error(exc) + + +def _to_session_fs_error(exc: Exception) -> SessionFSError: + code = SessionFSErrorCode.ENOENT if _is_enoent(exc) else SessionFSErrorCode.UNKNOWN + return SessionFSError(code=code, message=str(exc)) + + +def _is_enoent(exc: Exception) -> bool: + if isinstance(exc, FileNotFoundError): + return True + if isinstance(exc, OSError) and exc.errno == errno.ENOENT: + return True + return False diff --git a/python/e2e/test_session_fs.py b/python/e2e/test_session_fs.py index 4ff473b38..c2b4c95d3 100644 --- a/python/e2e/test_session_fs.py +++ b/python/e2e/test_session_fs.py @@ -14,14 +14,12 @@ from copilot import CopilotClient, SessionFsConfig, define_tool from copilot.client import ExternalServerConfig, SubprocessConfig from copilot.generated.rpc import ( - SessionFSExistsResult, - SessionFSReaddirResult, - SessionFSReaddirWithTypesResult, - SessionFSReadFileResult, - SessionFSStatResult, + SessionFSReaddirWithTypesEntry, + SessionFSReaddirWithTypesEntryType, ) from copilot.generated.session_events import SessionCompactionCompleteData, SessionEvent from copilot.session import PermissionHandler +from copilot.session_fs_provider import SessionFsFileInfo, SessionFsProvider from .testharness import E2ETestContext @@ -269,89 +267,80 @@ async def test_should_persist_plan_md_via_sessionfs( await session.disconnect() -class _SessionFsHandler: +class _TestSessionFsProvider(SessionFsProvider): def __init__(self, provider_root: Path, session_id: str): self._provider_root = provider_root self._session_id = session_id - async def read_file(self, params) -> SessionFSReadFileResult: - content = provider_path(self._provider_root, self._session_id, params.path).read_text( - encoding="utf-8" - ) - return SessionFSReadFileResult.from_dict({"content": content}) - - async def write_file(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - path.parent.mkdir(parents=True, exist_ok=True) - path.write_text(params.content, encoding="utf-8") - - async def append_file(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - path.parent.mkdir(parents=True, exist_ok=True) - with path.open("a", encoding="utf-8") as handle: - handle.write(params.content) - - async def exists(self, params) -> SessionFSExistsResult: - path = provider_path(self._provider_root, self._session_id, params.path) - return SessionFSExistsResult.from_dict({"exists": path.exists()}) - - async def stat(self, params) -> SessionFSStatResult: - path = provider_path(self._provider_root, self._session_id, params.path) - info = path.stat() - timestamp = dt.datetime.fromtimestamp(info.st_mtime, tz=dt.UTC).isoformat() - if timestamp.endswith("+00:00"): - timestamp = f"{timestamp[:-6]}Z" - return SessionFSStatResult.from_dict( - { - "isFile": not path.is_dir(), - "isDirectory": path.is_dir(), - "size": info.st_size, - "mtime": timestamp, - "birthtime": timestamp, - } + def _path(self, path: str) -> Path: + return provider_path(self._provider_root, self._session_id, path) + + async def read_file(self, path: str) -> str: + return self._path(path).read_text(encoding="utf-8") + + async def write_file(self, path: str, content: str) -> None: + p = self._path(path) + p.parent.mkdir(parents=True, exist_ok=True) + p.write_text(content, encoding="utf-8") + + async def append_file(self, path: str, content: str) -> None: + p = self._path(path) + p.parent.mkdir(parents=True, exist_ok=True) + with p.open("a", encoding="utf-8") as handle: + handle.write(content) + + async def exists(self, path: str) -> bool: + return self._path(path).exists() + + async def stat(self, path: str) -> SessionFsFileInfo: + p = self._path(path) + info = p.stat() + timestamp = dt.datetime.fromtimestamp(info.st_mtime, tz=dt.UTC) + return SessionFsFileInfo( + is_file=not p.is_dir(), + is_directory=p.is_dir(), + size=info.st_size, + mtime=timestamp, + birthtime=timestamp, ) - async def mkdir(self, params) -> None: - path = provider_path(self._provider_root, self._session_id, params.path) - if params.recursive: - path.mkdir(parents=True, exist_ok=True) + async def mkdir(self, path: str, recursive: bool, mode: int | None = None) -> None: + p = self._path(path) + if recursive: + p.mkdir(parents=True, exist_ok=True) else: - path.mkdir() + p.mkdir() - async def readdir(self, params) -> SessionFSReaddirResult: - entries = sorted( - entry.name - for entry in provider_path(self._provider_root, self._session_id, params.path).iterdir() - ) - return SessionFSReaddirResult.from_dict({"entries": entries}) + async def readdir(self, path: str) -> list[str]: + return sorted(entry.name for entry in self._path(path).iterdir()) - async def readdir_with_types(self, params) -> SessionFSReaddirWithTypesResult: + async def readdir_with_types( + self, path: str + ) -> list[SessionFSReaddirWithTypesEntry]: entries = [] - for entry in sorted( - provider_path(self._provider_root, self._session_id, params.path).iterdir(), - key=lambda item: item.name, - ): + for entry in sorted(self._path(path).iterdir(), key=lambda item: item.name): entries.append( - { - "name": entry.name, - "type": "directory" if entry.is_dir() else "file", - } + SessionFSReaddirWithTypesEntry( + name=entry.name, + type=SessionFSReaddirWithTypesEntryType.DIRECTORY + if entry.is_dir() + else SessionFSReaddirWithTypesEntryType.FILE, + ) ) - return SessionFSReaddirWithTypesResult.from_dict({"entries": entries}) + return entries - async def rm(self, params) -> None: - provider_path(self._provider_root, self._session_id, params.path).unlink() + async def rm(self, path: str, recursive: bool, force: bool) -> None: + self._path(path).unlink() - async def rename(self, params) -> None: - src = provider_path(self._provider_root, self._session_id, params.src) - dest = provider_path(self._provider_root, self._session_id, params.dest) - dest.parent.mkdir(parents=True, exist_ok=True) - src.rename(dest) + async def rename(self, src: str, dest: str) -> None: + d = self._path(dest) + d.parent.mkdir(parents=True, exist_ok=True) + self._path(src).rename(d) def create_test_session_fs_handler(provider_root: Path): def create_handler(session): - return _SessionFsHandler(provider_root, session.session_id) + return _TestSessionFsProvider(provider_root, session.session_id) return create_handler From 01d231cb473999bbfc193dd26664fc0ffafdfa88 Mon Sep 17 00:00:00 2001 From: Steve Sanderson Date: Mon, 20 Apr 2026 20:06:27 +0100 Subject: [PATCH 10/10] fix: Python JSON-RPC dispatch sends null for void results The _dispatch_request method was converting None handler results to {} (empty dict) before sending JSON-RPC responses. The runtime expects null for void operations (mkdir, writeFile, appendFile). Receiving {} caused the runtime to fall back to local filesystem for subsequent writes. Also removes debug tracing from session_fs_provider.py adapter. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/copilot/_jsonrpc.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/python/copilot/_jsonrpc.py b/python/copilot/_jsonrpc.py index 287f1b965..030c5b39b 100644 --- a/python/copilot/_jsonrpc.py +++ b/python/copilot/_jsonrpc.py @@ -328,7 +328,8 @@ def _handle_message(self, message: dict): self._handle_request(message) def _handle_request(self, message: dict): - handler = self.request_handlers.get(message["method"]) + method = message.get("method", "") + handler = self.request_handlers.get(method) if not handler: if self._loop: asyncio.run_coroutine_threadsafe( @@ -351,17 +352,15 @@ async def _dispatch_request(self, message: dict, handler: RequestHandler): outcome = handler(params) if inspect.isawaitable(outcome): outcome = await outcome - if outcome is None: - outcome = {} - if not isinstance(outcome, dict): - raise ValueError("Request handler must return a dict") + if outcome is not None and not isinstance(outcome, dict): + raise ValueError(f"Request handler must return a dict, got {type(outcome).__name__}") await self._send_response(message["id"], outcome) except JsonRpcError as exc: await self._send_error_response(message["id"], exc.code, exc.message, exc.data) except Exception as exc: # pylint: disable=broad-except await self._send_error_response(message["id"], -32603, str(exc), None) - async def _send_response(self, request_id: str, result: dict): + async def _send_response(self, request_id: str, result: dict | None): response = { "jsonrpc": "2.0", "id": request_id,