diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index a6d3efe77..3522ad60b 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1329,7 +1329,7 @@ await Rpc.SessionFs.SetProviderAsync( _options.SessionFs.InitialCwd, _options.SessionFs.SessionStatePath, _options.SessionFs.Conventions, - cancellationToken); + cancellationToken: cancellationToken); } private void ConfigureSessionFsHandlers(CopilotSession session, Func? createSessionFsHandler) diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index b89fcc92f..5880ffc85 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -168,7 +168,7 @@ public sealed class ModelPolicy { /// Current policy state for this model. [JsonPropertyName("state")] - public string State { get; set; } = string.Empty; + public ModelPolicyState State { get; set; } /// Usage terms or conditions for this model. [JsonPropertyName("terms")] @@ -274,7 +274,7 @@ internal sealed class ToolsListRequest /// Schema for the `AccountQuotaSnapshot` type. public sealed class AccountQuotaSnapshot { - /// Number of requests included in the entitlement. + /// Number of requests included in the entitlement, or -1 for unlimited entitlements. [JsonPropertyName("entitlementRequests")] public long EntitlementRequests { get; set; } @@ -297,7 +297,7 @@ public sealed class AccountQuotaSnapshot /// Date when the quota resets (ISO 8601 string). [JsonPropertyName("resetDate")] - public string? ResetDate { get; set; } + public DateTimeOffset? ResetDate { get; set; } /// Whether usage is still permitted after quota exhaustion. [JsonPropertyName("usageAllowedWithExhaustedQuota")] @@ -339,11 +339,11 @@ public sealed class DiscoveredMcpServer [JsonPropertyName("name")] public string Name { get; set; } = string.Empty; - /// Configuration source. + /// Configuration source: user, workspace, plugin, or builtin. [JsonPropertyName("source")] - public DiscoveredMcpServerSource Source { get; set; } + public McpServerSource Source { get; set; } - /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). + /// Server transport type: stdio, http, sse, or memory. [JsonPropertyName("type")] public DiscoveredMcpServerType? Type { get; set; } } @@ -375,7 +375,7 @@ public sealed class McpConfigList /// MCP server name and configuration to add to user configuration. internal sealed class McpConfigAddRequest { - /// MCP server configuration (local/stdio or remote/http). + /// MCP server configuration (stdio process or remote HTTP/SSE). [JsonPropertyName("config")] public object Config { get; set; } = null!; @@ -390,7 +390,7 @@ internal sealed class McpConfigAddRequest /// MCP server name and replacement configuration to write to user configuration. internal sealed class McpConfigUpdateRequest { - /// MCP server configuration (local/stdio or remote/http). + /// MCP server configuration (stdio process or remote HTTP/SSE). [JsonPropertyName("config")] public object Config { get; set; } = null!; @@ -454,7 +454,7 @@ public sealed class ServerSkill /// Source location type (e.g., project, personal-copilot, plugin, builtin). [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + public SkillSource Source { get; set; } /// Whether the skill can be invoked by the user as a slash command. [JsonPropertyName("userInvocable")] @@ -497,9 +497,21 @@ public sealed class SessionFsSetProviderResult public bool Success { get; set; } } +/// Optional capabilities declared by the provider. +public sealed class SessionFsSetProviderCapabilities +{ + /// Whether the provider supports SQLite query/exists operations. + [JsonPropertyName("sqlite")] + public bool? Sqlite { get; set; } +} + /// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. internal sealed class SessionFsSetProviderRequest { + /// Optional capabilities declared by the provider. + [JsonPropertyName("capabilities")] + public SessionFsSetProviderCapabilities? Capabilities { get; set; } + /// Path conventions used by this filesystem. [JsonPropertyName("conventions")] public SessionFsSetProviderConventions Conventions { get; set; } @@ -570,7 +582,7 @@ public sealed class ConnectedRemoteSessionMetadata /// Last session update time as an ISO 8601 string. [JsonPropertyName("modifiedTime")] - public string ModifiedTime { get; set; } = string.Empty; + public DateTimeOffset ModifiedTime { get; set; } /// Optional friendly session name. [JsonPropertyName("name")] @@ -594,11 +606,11 @@ public sealed class ConnectedRemoteSessionMetadata /// Remote session staleness deadline as an ISO 8601 string. [JsonPropertyName("staleAt")] - public string? StaleAt { get; set; } + public DateTimeOffset? StaleAt { get; set; } /// Session start time as an ISO 8601 string. [JsonPropertyName("startTime")] - public string StartTime { get; set; } = string.Empty; + public DateTimeOffset StartTime { get; set; } /// Remote session state returned by the backing service. [JsonPropertyName("state")] @@ -685,6 +697,8 @@ public sealed class SessionAuthStatus public string? CopilotPlan { get; set; } /// Authentication host URL. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("host")] public string? Host { get; set; } @@ -833,7 +847,7 @@ internal sealed class SessionModeGetRequest /// Agent interaction mode to apply to the session. internal sealed class ModeSetRequest { - /// The agent mode. Valid values: "interactive", "plan", "autopilot". + /// The session mode the agent is operating in. [JsonPropertyName("mode")] public SessionMode Mode { get; set; } @@ -1331,10 +1345,10 @@ public partial class TaskInfoAgent : TaskInfo [JsonPropertyName("error")] public string? Error { get; set; } - /// How the agent is currently being managed by the runtime. + /// Whether task execution is synchronously awaited or managed in the background. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("executionMode")] - public TaskAgentInfoExecutionMode? ExecutionMode { get; set; } + public TaskExecutionMode? ExecutionMode { get; set; } /// Unique task identifier. [JsonPropertyName("id")] @@ -1370,7 +1384,7 @@ public partial class TaskInfoAgent : TaskInfo /// Current lifecycle status of the task. [JsonPropertyName("status")] - public required TaskAgentInfoStatus Status { get; set; } + public required TaskStatus Status { get; set; } /// Tool call ID associated with this agent task. [JsonPropertyName("toolCallId")] @@ -1408,10 +1422,10 @@ public partial class TaskInfoShell : TaskInfo [JsonPropertyName("description")] public required string Description { get; set; } - /// Whether the shell command is currently sync-waited or background-managed. + /// Whether task execution is synchronously awaited or managed in the background. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("executionMode")] - public TaskShellInfoExecutionMode? ExecutionMode { get; set; } + public TaskExecutionMode? ExecutionMode { get; set; } /// Unique task identifier. [JsonPropertyName("id")] @@ -1433,7 +1447,7 @@ public partial class TaskInfoShell : TaskInfo /// Current lifecycle status of the task. [JsonPropertyName("status")] - public required TaskShellInfoStatus Status { get; set; } + public required TaskStatus Status { get; set; } } /// Background tasks currently tracked by the session. @@ -1574,9 +1588,9 @@ public sealed class Skill [JsonPropertyName("path")] public string? Path { get; set; } - /// Source location type (e.g., project, personal, plugin). + /// Source location type (e.g., project, personal-copilot, plugin, builtin). [JsonPropertyName("source")] - public string Source { get; set; } = string.Empty; + public SkillSource Source { get; set; } /// Whether the skill can be invoked by the user as a slash command. [JsonPropertyName("userInvocable")] @@ -1737,6 +1751,8 @@ internal sealed class SessionMcpReloadRequest public sealed class McpOauthLoginResult { /// URL the caller should open in a browser to complete OAuth. Omitted when cached tokens were still valid and no browser interaction was needed — the server is already reconnected in that case. When present, the runtime starts the callback listener before returning and continues the flow in the background; completion is signaled via session.mcp_server_status_changed. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("authorizationUrl")] public string? AuthorizationUrl { get; set; } } @@ -2066,10 +2082,10 @@ public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvoc [JsonPropertyName("displayPrompt")] public required string DisplayPrompt { get; set; } - /// Optional target session mode. + /// Optional target session mode for the agent prompt. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("mode")] - public SlashCommandAgentPromptMode? Mode { get; set; } + public SessionMode? Mode { get; set; } /// Prompt to submit to the agent. [JsonPropertyName("prompt")] @@ -2989,6 +3005,8 @@ public sealed class RemoteEnableResult public bool RemoteSteerable { get; set; } /// GitHub frontend URL for this session. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public string? Url { get; set; } } @@ -3271,6 +3289,67 @@ public sealed class SessionFsRenameRequest public string Src { get; set; } = string.Empty; } +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +public sealed class SessionFsSqliteQueryResult +{ + /// Column names from the result set. + [JsonPropertyName("columns")] + public IList Columns { get => field ??= []; set; } + + /// Describes a filesystem error. + [JsonPropertyName("error")] + public SessionFsError? Error { get; set; } + + /// Last inserted row ID (for INSERT). + [JsonPropertyName("lastInsertRowid")] + public double? LastInsertRowid { get; set; } + + /// For SELECT: array of row objects. For others: empty array. + [JsonPropertyName("rows")] + public IList> Rows { get => field ??= []; set; } + + /// Number of rows affected (for INSERT/UPDATE/DELETE). + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("rowsAffected")] + public long RowsAffected { get; set; } +} + +/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. +public sealed class SessionFsSqliteQueryRequest +{ + /// Optional named bind parameters. + [JsonPropertyName("params")] + public IDictionary? Params { get; set; } + + /// SQL query to execute. + [JsonPropertyName("query")] + public string Query { get; set; } = string.Empty; + + /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). + [JsonPropertyName("queryType")] + public SessionFsSqliteQueryType QueryType { get; set; } + + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + +/// Indicates whether the per-session SQLite database already exists. +public sealed class SessionFsSqliteExistsResult +{ + /// Whether the session database already exists. + [JsonPropertyName("exists")] + public bool Exists { get; set; } +} + +/// Identifies the target session. +public sealed class SessionFsSqliteExistsRequest +{ + /// Target session identifier. + [JsonPropertyName("sessionId")] + public string SessionId { get; set; } = string.Empty; +} + /// Model capability category for grouping in the model picker. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -3404,48 +3483,45 @@ public override void Write(Utf8JsonWriter writer, ModelPickerPriceCategory value } -/// Configuration source. +/// Current policy state for this model. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct DiscoveredMcpServerSource : IEquatable +public readonly struct ModelPolicyState : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public DiscoveredMcpServerSource(string value) + public ModelPolicyState(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the user value. - public static DiscoveredMcpServerSource User { get; } = new("user"); - - /// Gets the workspace value. - public static DiscoveredMcpServerSource Workspace { get; } = new("workspace"); + /// Gets the enabled value. + public static ModelPolicyState Enabled { get; } = new("enabled"); - /// Gets the plugin value. - public static DiscoveredMcpServerSource Plugin { get; } = new("plugin"); + /// Gets the disabled value. + public static ModelPolicyState Disabled { get; } = new("disabled"); - /// Gets the builtin value. - public static DiscoveredMcpServerSource Builtin { get; } = new("builtin"); + /// Gets the unconfigured value. + public static ModelPolicyState Unconfigured { get; } = new("unconfigured"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(DiscoveredMcpServerSource left, DiscoveredMcpServerSource right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ModelPolicyState left, ModelPolicyState right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(DiscoveredMcpServerSource left, DiscoveredMcpServerSource right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ModelPolicyState left, ModelPolicyState right) => !(left == right); /// - public override bool Equals(object? obj) => obj is DiscoveredMcpServerSource other && Equals(other); + public override bool Equals(object? obj) => obj is ModelPolicyState other && Equals(other); /// - public bool Equals(DiscoveredMcpServerSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ModelPolicyState other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -3453,26 +3529,26 @@ public DiscoveredMcpServerSource(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override DiscoveredMcpServerSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ModelPolicyState Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, DiscoveredMcpServerSource value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ModelPolicyState value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(DiscoveredMcpServerSource)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ModelPolicyState)); } } } -/// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio). +/// Server transport type: stdio, http, sse, or memory. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] public readonly struct DiscoveredMcpServerType : IEquatable @@ -3807,71 +3883,6 @@ public override void Write(Utf8JsonWriter writer, AuthInfoType value, JsonSerial } -/// The agent mode. Valid values: "interactive", "plan", "autopilot". -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SessionMode : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SessionMode(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the interactive value. - public static SessionMode Interactive { get; } = new("interactive"); - - /// Gets the plan value. - public static SessionMode Plan { get; } = new("plan"); - - /// Gets the autopilot value. - public static SessionMode Autopilot { get; } = new("autopilot"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); - - /// - public bool Equals(SessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); - } - } -} - - /// Defines the allowed values. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -4073,43 +4084,43 @@ public override void Write(Utf8JsonWriter writer, InstructionsSourcesType value, } -/// How the agent is currently being managed by the runtime. +/// Whether task execution is synchronously awaited or managed in the background. [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskAgentInfoExecutionMode : IEquatable +public readonly struct TaskExecutionMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public TaskAgentInfoExecutionMode(string value) + public TaskExecutionMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; /// Gets the sync value. - public static TaskAgentInfoExecutionMode Sync { get; } = new("sync"); + public static TaskExecutionMode Sync { get; } = new("sync"); /// Gets the background value. - public static TaskAgentInfoExecutionMode Background { get; } = new("background"); + public static TaskExecutionMode Background { get; } = new("background"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskAgentInfoExecutionMode left, TaskAgentInfoExecutionMode right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskExecutionMode left, TaskExecutionMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskAgentInfoExecutionMode left, TaskAgentInfoExecutionMode right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskExecutionMode left, TaskExecutionMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskAgentInfoExecutionMode other && Equals(other); + public override bool Equals(object? obj) => obj is TaskExecutionMode other && Equals(other); /// - public bool Equals(TaskAgentInfoExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -4117,20 +4128,20 @@ public TaskAgentInfoExecutionMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override TaskAgentInfoExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskAgentInfoExecutionMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskExecutionMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskAgentInfoExecutionMode)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskExecutionMode)); } } } @@ -4140,48 +4151,48 @@ public override void Write(Utf8JsonWriter writer, TaskAgentInfoExecutionMode val [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct TaskAgentInfoStatus : IEquatable +public readonly struct TaskStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public TaskAgentInfoStatus(string value) + public TaskStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; /// Gets the running value. - public static TaskAgentInfoStatus Running { get; } = new("running"); + public static TaskStatus Running { get; } = new("running"); /// Gets the idle value. - public static TaskAgentInfoStatus Idle { get; } = new("idle"); + public static TaskStatus Idle { get; } = new("idle"); /// Gets the completed value. - public static TaskAgentInfoStatus Completed { get; } = new("completed"); + public static TaskStatus Completed { get; } = new("completed"); /// Gets the failed value. - public static TaskAgentInfoStatus Failed { get; } = new("failed"); + public static TaskStatus Failed { get; } = new("failed"); /// Gets the cancelled value. - public static TaskAgentInfoStatus Cancelled { get; } = new("cancelled"); + public static TaskStatus Cancelled { get; } = new("cancelled"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskAgentInfoStatus left, TaskAgentInfoStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(TaskStatus left, TaskStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskAgentInfoStatus left, TaskAgentInfoStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(TaskStatus left, TaskStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is TaskAgentInfoStatus other && Equals(other); + public override bool Equals(object? obj) => obj is TaskStatus other && Equals(other); /// - public bool Equals(TaskAgentInfoStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(TaskStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -4189,20 +4200,20 @@ public TaskAgentInfoStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override TaskAgentInfoStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override TaskStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, TaskAgentInfoStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, TaskStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskAgentInfoStatus)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskStatus)); } } } @@ -4271,285 +4282,6 @@ public override void Write(Utf8JsonWriter writer, TaskShellInfoAttachmentMode va } -/// Whether the shell command is currently sync-waited or background-managed. -[Experimental(Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct TaskShellInfoExecutionMode : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public TaskShellInfoExecutionMode(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the sync value. - public static TaskShellInfoExecutionMode Sync { get; } = new("sync"); - - /// Gets the background value. - public static TaskShellInfoExecutionMode Background { get; } = new("background"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskShellInfoExecutionMode left, TaskShellInfoExecutionMode right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskShellInfoExecutionMode left, TaskShellInfoExecutionMode right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is TaskShellInfoExecutionMode other && Equals(other); - - /// - public bool Equals(TaskShellInfoExecutionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override TaskShellInfoExecutionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, TaskShellInfoExecutionMode value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoExecutionMode)); - } - } -} - - -/// Current lifecycle status of the task. -[Experimental(Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct TaskShellInfoStatus : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public TaskShellInfoStatus(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the running value. - public static TaskShellInfoStatus Running { get; } = new("running"); - - /// Gets the idle value. - public static TaskShellInfoStatus Idle { get; } = new("idle"); - - /// Gets the completed value. - public static TaskShellInfoStatus Completed { get; } = new("completed"); - - /// Gets the failed value. - public static TaskShellInfoStatus Failed { get; } = new("failed"); - - /// Gets the cancelled value. - public static TaskShellInfoStatus Cancelled { get; } = new("cancelled"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(TaskShellInfoStatus left, TaskShellInfoStatus right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(TaskShellInfoStatus left, TaskShellInfoStatus right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is TaskShellInfoStatus other && Equals(other); - - /// - public bool Equals(TaskShellInfoStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override TaskShellInfoStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, TaskShellInfoStatus value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(TaskShellInfoStatus)); - } - } -} - - -/// Configuration source: user, workspace, plugin, or builtin. -[Experimental(Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct McpServerSource : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public McpServerSource(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the user value. - public static McpServerSource User { get; } = new("user"); - - /// Gets the workspace value. - public static McpServerSource Workspace { get; } = new("workspace"); - - /// Gets the plugin value. - public static McpServerSource Plugin { get; } = new("plugin"); - - /// Gets the builtin value. - public static McpServerSource Builtin { get; } = new("builtin"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpServerSource left, McpServerSource right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpServerSource left, McpServerSource right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is McpServerSource other && Equals(other); - - /// - public bool Equals(McpServerSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override McpServerSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, McpServerSource value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerSource)); - } - } -} - - -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. -[Experimental(Diagnostics.Experimental)] -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct McpServerStatus : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public McpServerStatus(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the connected value. - public static McpServerStatus Connected { get; } = new("connected"); - - /// Gets the failed value. - public static McpServerStatus Failed { get; } = new("failed"); - - /// Gets the needs-auth value. - public static McpServerStatus NeedsAuth { get; } = new("needs-auth"); - - /// Gets the pending value. - public static McpServerStatus Pending { get; } = new("pending"); - - /// Gets the disabled value. - public static McpServerStatus Disabled { get; } = new("disabled"); - - /// Gets the not_configured value. - public static McpServerStatus NotConfigured { get; } = new("not_configured"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpServerStatus left, McpServerStatus right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpServerStatus left, McpServerStatus right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is McpServerStatus other && Equals(other); - - /// - public bool Equals(McpServerStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override McpServerStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, McpServerStatus value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerStatus)); - } - } -} - - /// Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/). [Experimental(Diagnostics.Experimental)] [JsonConverter(typeof(Converter))] @@ -4806,71 +4538,6 @@ public override void Write(Utf8JsonWriter writer, SlashCommandKind value, JsonSe } -/// Optional target session mode. -[JsonConverter(typeof(Converter))] -[DebuggerDisplay("{Value,nq}")] -public readonly struct SlashCommandAgentPromptMode : IEquatable -{ - private readonly string? _value; - - /// Initializes a new instance of the struct. - /// The value to associate with this . - [JsonConstructor] - public SlashCommandAgentPromptMode(string value) - { - ArgumentException.ThrowIfNullOrWhiteSpace(value); - _value = value; - } - - /// Gets the value associated with this . - public string Value => _value ?? string.Empty; - - /// Gets the interactive value. - public static SlashCommandAgentPromptMode Interactive { get; } = new("interactive"); - - /// Gets the plan value. - public static SlashCommandAgentPromptMode Plan { get; } = new("plan"); - - /// Gets the autopilot value. - public static SlashCommandAgentPromptMode Autopilot { get; } = new("autopilot"); - - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(SlashCommandAgentPromptMode left, SlashCommandAgentPromptMode right) => left.Equals(right); - - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(SlashCommandAgentPromptMode left, SlashCommandAgentPromptMode right) => !(left == right); - - /// - public override bool Equals(object? obj) => obj is SlashCommandAgentPromptMode other && Equals(other); - - /// - public bool Equals(SlashCommandAgentPromptMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); - - /// - public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); - - /// - public override string ToString() => Value; - - /// Provides a for serializing instances. - [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter - { - /// - public override SlashCommandAgentPromptMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) - { - return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); - } - - /// - public override void Write(Utf8JsonWriter writer, SlashCommandAgentPromptMode value, JsonSerializerOptions options) - { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandAgentPromptMode)); - } - } -} - - /// The user's response: accept (submitted), decline (rejected), or cancel (dismissed). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -5191,6 +4858,71 @@ public override void Write(Utf8JsonWriter writer, SessionFsReaddirWithTypesEntry } +/// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected). +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionFsSqliteQueryType : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionFsSqliteQueryType(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the exec value. + public static SessionFsSqliteQueryType Exec { get; } = new("exec"); + + /// Gets the query value. + public static SessionFsSqliteQueryType Query { get; } = new("query"); + + /// Gets the run value. + public static SessionFsSqliteQueryType Run { get; } = new("run"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionFsSqliteQueryType left, SessionFsSqliteQueryType right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionFsSqliteQueryType other && Equals(other); + + /// + public bool Equals(SessionFsSqliteQueryType other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionFsSqliteQueryType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionFsSqliteQueryType value, JsonSerializerOptions options) + { + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionFsSqliteQueryType)); + } + } +} + + /// Provides server-scoped RPC methods (no session required). public sealed class ServerRpc { @@ -5374,7 +5106,7 @@ public async Task ListAsync(CancellationToken cancellationToken = /// Adds an MCP server to user configuration. /// Unique name for the MCP server. - /// MCP server configuration (local/stdio or remote/http). + /// MCP server configuration (stdio process or remote HTTP/SSE). /// The to monitor for cancellation requests. The default is . public async Task AddAsync(string name, object config, CancellationToken cancellationToken = default) { @@ -5387,7 +5119,7 @@ public async Task AddAsync(string name, object config, CancellationToken cancell /// Updates an MCP server in user configuration. /// Name of the MCP server to update. - /// MCP server configuration (local/stdio or remote/http). + /// MCP server configuration (stdio process or remote HTTP/SSE). /// The to monitor for cancellation requests. The default is . public async Task UpdateAsync(string name, object config, CancellationToken cancellationToken = default) { @@ -5496,14 +5228,15 @@ internal ServerSessionFsApi(JsonRpc rpc) /// Initial working directory for sessions. /// Path within each session's SessionFs where the runtime stores files for that session. /// Path conventions used by this filesystem. + /// Optional capabilities declared by the provider. /// The to monitor for cancellation requests. The default is . /// Indicates whether the calling client was registered as the session filesystem provider. - public async Task SetProviderAsync(string initialCwd, string sessionStatePath, SessionFsSetProviderConventions conventions, CancellationToken cancellationToken = default) + public async Task SetProviderAsync(string initialCwd, string sessionStatePath, SessionFsSetProviderConventions conventions, SessionFsSetProviderCapabilities? capabilities = null, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(initialCwd); ArgumentNullException.ThrowIfNull(sessionStatePath); - var request = new SessionFsSetProviderRequest { InitialCwd = initialCwd, SessionStatePath = sessionStatePath, Conventions = conventions }; + var request = new SessionFsSetProviderRequest { InitialCwd = initialCwd, SessionStatePath = sessionStatePath, Conventions = conventions, Capabilities = capabilities }; return await CopilotClient.InvokeRpcAsync(_rpc, "sessionFs.setProvider", [request], cancellationToken); } } @@ -5789,7 +5522,7 @@ internal ModeApi(CopilotSession session) /// Gets the current agent interaction mode. /// The to monitor for cancellation requests. The default is . - /// The agent mode. Valid values: "interactive", "plan", "autopilot". + /// The session mode the agent is operating in. public async Task GetAsync(CancellationToken cancellationToken = default) { _session.ThrowIfDisposed(); @@ -5799,7 +5532,7 @@ public async Task GetAsync(CancellationToken cancellationToken = de } /// Sets the current agent interaction mode. - /// The agent mode. Valid values: "interactive", "plan", "autopilot". + /// The session mode the agent is operating in. /// The to monitor for cancellation requests. The default is . public async Task SetAsync(SessionMode mode, CancellationToken cancellationToken = default) { @@ -6756,6 +6489,16 @@ public interface ISessionFsHandler /// The to monitor for cancellation requests. The default is . /// Describes a filesystem error. Task RenameAsync(SessionFsRenameRequest request, CancellationToken cancellationToken = default); + /// Executes a SQLite query against the per-session database. + /// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + /// The to monitor for cancellation requests. The default is . + /// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. + Task SqliteQueryAsync(SessionFsSqliteQueryRequest request, CancellationToken cancellationToken = default); + /// Checks whether the per-session SQLite database already exists, without creating it. + /// Identifies the target session. + /// The to monitor for cancellation requests. The default is . + /// Indicates whether the per-session SQLite database already exists. + Task SqliteExistsAsync(SessionFsSqliteExistsRequest request, CancellationToken cancellationToken = default); } /// Provides all client session API handler groups for a session. @@ -6835,6 +6578,18 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, 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}"); + return await handler.SqliteQueryAsync(request, cancellationToken); + }), singleObjectParam: true); + rpc.SetLocalRpcMethod("sessionFs.sqliteExists", (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}"); + return await handler.SqliteExistsAsync(request, cancellationToken); + }), singleObjectParam: true); } } @@ -6849,7 +6604,11 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncReasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh"). + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } @@ -1281,7 +1281,7 @@ public sealed partial class SessionResumeData [JsonPropertyName("eventCount")] public required double EventCount { get; set; } - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh"). + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } @@ -1393,8 +1393,9 @@ public sealed partial class SessionScheduleCreatedData public required long Id { get; set; } /// Interval between ticks in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonPropertyName("intervalMs")] - public required long IntervalMs { get; set; } + public required TimeSpan IntervalMs { get; set; } /// Prompt text that gets enqueued on every tick. [JsonPropertyName("prompt")] @@ -1498,13 +1499,13 @@ public sealed partial class SessionModelChangeData /// Agent mode change details including previous and new modes. public sealed partial class SessionModeChangedData { - /// Agent mode after the change (e.g., "interactive", "plan", "autopilot"). + /// The session mode the agent is operating in. [JsonPropertyName("newMode")] - public required string NewMode { get; set; } + public required SessionMode NewMode { get; set; } - /// Agent mode before the change (e.g., "interactive", "plan", "autopilot"). + /// The session mode the agent is operating in. [JsonPropertyName("previousMode")] - public required string PreviousMode { get; set; } + public required SessionMode PreviousMode { get; set; } } /// Plan file operation details indicating what changed. @@ -1540,6 +1541,8 @@ public sealed partial class SessionHandoffData public required DateTimeOffset HandoffTime { get; set; } /// GitHub host URL for the source session (e.g., https://github.com or https://tenant.ghe.com). + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("host")] public string? Host { get; set; } @@ -1667,8 +1670,9 @@ public sealed partial class SessionShutdownData public double? ToolDefinitionsTokens { get; set; } /// Cumulative time spent in API calls during the session, in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonPropertyName("totalApiDurationMs")] - public required double TotalApiDurationMs { get; set; } + public required TimeSpan TotalApiDurationMs { get; set; } /// Session-wide accumulated nano-AI units cost. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2134,9 +2138,10 @@ public sealed partial class AssistantUsageData public double? Cost { get; set; } /// Duration of the API call in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("duration")] - public double? Duration { get; set; } + public TimeSpan? Duration { get; set; } /// What initiated this API call (e.g., "sub-agent", "mcp-sampling"); absent for user-initiated calls. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2149,9 +2154,10 @@ public sealed partial class AssistantUsageData public double? InputTokens { get; set; } /// Average inter-token latency in milliseconds. Only available for streaming requests. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("interTokenLatencyMs")] - public double? InterTokenLatencyMs { get; set; } + public TimeSpan? InterTokenLatencyMs { get; set; } /// Model identifier used for this API call. [JsonPropertyName("model")] @@ -2179,7 +2185,7 @@ public sealed partial class AssistantUsageData [JsonPropertyName("quotaSnapshots")] public IDictionary? QuotaSnapshots { get; set; } - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh"). + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max"). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("reasoningEffort")] public string? ReasoningEffort { get; set; } @@ -2190,9 +2196,10 @@ public sealed partial class AssistantUsageData public double? ReasoningTokens { get; set; } /// Time to first token in milliseconds. Only available for streaming requests. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("ttftMs")] - public double? TtftMs { get; set; } + public TimeSpan? TtftMs { get; set; } } /// Failed LLM API call metadata for telemetry. @@ -2204,9 +2211,10 @@ public sealed partial class ModelCallFailureData public string? ApiCallId { get; set; } /// Duration of the failed API call in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("durationMs")] - public double? DurationMs { get; set; } + public TimeSpan? DurationMs { get; set; } /// Raw provider/runtime error message for restricted telemetry. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2453,9 +2461,10 @@ public sealed partial class SubagentCompletedData public required string AgentName { get; set; } /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("durationMs")] - public double? DurationMs { get; set; } + public TimeSpan? DurationMs { get; set; } /// Model used by the sub-agent. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2489,9 +2498,10 @@ public sealed partial class SubagentFailedData public required string AgentName { get; set; } /// Wall-clock duration of the sub-agent execution in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("durationMs")] - public double? DurationMs { get; set; } + public TimeSpan? DurationMs { get; set; } /// Error message describing why the sub-agent failed. [JsonPropertyName("error")] @@ -2790,6 +2800,8 @@ public sealed partial class McpOauthRequiredData public required string ServerName { get; set; } /// URL of the MCP server that requires OAuth. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("serverUrl")] public required string ServerUrl { get; set; } @@ -2945,9 +2957,9 @@ public sealed partial class AutoModeSwitchCompletedData [JsonPropertyName("requestId")] public required string RequestId { get; set; } - /// The user's choice: 'yes', 'yes_always', or 'no'. + /// The user's auto-mode-switch choice. [JsonPropertyName("response")] - public required string Response { get; set; } + public required AutoModeSwitchResponse Response { get; set; } } /// SDK command registration change notification. @@ -2970,17 +2982,17 @@ public sealed partial class CapabilitiesChangedData /// Plan approval request with plan content and available user actions. public sealed partial class ExitPlanModeRequestedData { - /// Available actions the user can take (e.g., approve, edit, reject). + /// Available actions the user can take. [JsonPropertyName("actions")] - public required string[] Actions { get; set; } + public required ExitPlanModeAction[] Actions { get; set; } /// Full content of the plan file. [JsonPropertyName("planContent")] public required string PlanContent { get; set; } - /// The recommended action for the user to take. + /// Recommended action to preselect for the user. [JsonPropertyName("recommendedAction")] - public required string RecommendedAction { get; set; } + public required ExitPlanModeAction RecommendedAction { get; set; } /// Unique identifier for this request; used to respond via session.respondToExitPlanMode(). [JsonPropertyName("requestId")] @@ -3013,10 +3025,10 @@ public sealed partial class ExitPlanModeCompletedData [JsonPropertyName("requestId")] public required string RequestId { get; set; } - /// Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only'). + /// Action selected by the user. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("selectedAction")] - public string? SelectedAction { get; set; } + public ExitPlanModeAction? SelectedAction { get; set; } } /// Schema for the `ToolsUpdatedData` type. @@ -3071,9 +3083,9 @@ public sealed partial class SessionMcpServerStatusChangedData [JsonPropertyName("serverName")] public required string ServerName { get; set; } - /// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured. + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonPropertyName("status")] - public required McpServerStatusChangedStatus Status { get; set; } + public required McpServerStatus Status { get; set; } } /// Schema for the `ExtensionsLoadedData` type. @@ -3297,9 +3309,10 @@ public sealed partial class CompactionCompleteCompactionTokensUsed public CompactionCompleteCompactionTokensUsedCopilotUsage? CopilotUsage { get; set; } /// Duration of the compaction LLM call in milliseconds. + [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("duration")] - public double? Duration { get; set; } + public TimeSpan? Duration { get; set; } /// Input tokens consumed by the compaction LLM call. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -3458,6 +3471,8 @@ public sealed partial class UserMessageAttachmentGithubReference : UserMessageAt public required string Title { get; set; } /// URL to the referenced item on GitHub. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public required string Url { get; set; } } @@ -4147,6 +4162,8 @@ public sealed partial class PermissionRequestShellCommand public sealed partial class PermissionRequestShellPossibleUrl { /// URL that may be accessed by the command. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public required string Url { get; set; } } @@ -4308,6 +4325,8 @@ public sealed partial class PermissionRequestUrl : PermissionRequest public string? ToolCallId { get; set; } /// URL to be fetched. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public required string Url { get; set; } } @@ -4619,6 +4638,8 @@ public sealed partial class PermissionPromptRequestUrl : PermissionPromptRequest public string? ToolCallId { get; set; } /// URL to be fetched. + [Url] + [StringSyntax(StringSyntaxAttribute.Uri)] [JsonPropertyName("url")] public required string Url { get; set; } } @@ -4634,7 +4655,7 @@ public sealed partial class PermissionPromptRequestMemory : PermissionPromptRequ /// Whether this is a store or vote memory operation. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("action")] - public PermissionPromptRequestMemoryAction? Action { get; set; } + public PermissionRequestMemoryAction? Action { get; set; } /// Source references for the stored fact (store only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -4644,7 +4665,7 @@ public sealed partial class PermissionPromptRequestMemory : PermissionPromptRequ /// Vote direction (vote only). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("direction")] - public PermissionPromptRequestMemoryDirection? Direction { get; set; } + public PermissionRequestMemoryDirection? Direction { get; set; } /// The fact being stored or voted on. [JsonPropertyName("fact")] @@ -5177,9 +5198,9 @@ public sealed partial class SkillsLoadedSkill [JsonPropertyName("path")] public string? Path { get; set; } - /// Source location type of the skill (e.g., project, personal, plugin). + /// Source location type (e.g., project, personal-copilot, plugin, builtin). [JsonPropertyName("source")] - public required string Source { get; set; } + public required SkillSource Source { get; set; } /// Whether the skill can be invoked by the user as a slash command. [JsonPropertyName("userInvocable")] @@ -5240,11 +5261,11 @@ public sealed partial class McpServersLoadedServer /// Configuration source: user, workspace, plugin, or builtin. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] - public string? Source { get; set; } + public McpServerSource? Source { get; set; } /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonPropertyName("status")] - public required McpServersLoadedServerStatus Status { get; set; } + public required McpServerStatus Status { get; set; } } /// Schema for the `ExtensionsLoadedExtension` type. @@ -5393,6 +5414,70 @@ public override void Write(Utf8JsonWriter writer, ReasoningSummary value, JsonSe } } +/// The session mode the agent is operating in. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct SessionMode : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public SessionMode(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the interactive value. + public static SessionMode Interactive { get; } = new("interactive"); + + /// Gets the plan value. + public static SessionMode Plan { get; } = new("plan"); + + /// Gets the autopilot value. + public static SessionMode Autopilot { get; } = new("autopilot"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SessionMode left, SessionMode right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SessionMode left, SessionMode right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is SessionMode other && Equals(other); + + /// + public bool Equals(SessionMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override SessionMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, SessionMode value, JsonSerializerOptions options) + { + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SessionMode)); + } + } +} + /// The type of operation performed on the plan file. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] @@ -6332,42 +6417,45 @@ public override void Write(Utf8JsonWriter writer, PermissionRequestMemoryDirecti } } -/// Whether this is a store or vote memory operation. +/// Underlying permission kind that needs path approval. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionPromptRequestMemoryAction : IEquatable +public readonly struct PermissionPromptRequestPathAccessKind : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionPromptRequestMemoryAction(string value) + public PermissionPromptRequestPathAccessKind(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the store value. - public static PermissionPromptRequestMemoryAction Store { get; } = new("store"); + /// Gets the read value. + public static PermissionPromptRequestPathAccessKind Read { get; } = new("read"); - /// Gets the vote value. - public static PermissionPromptRequestMemoryAction Vote { get; } = new("vote"); + /// Gets the shell value. + public static PermissionPromptRequestPathAccessKind Shell { get; } = new("shell"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionPromptRequestMemoryAction left, PermissionPromptRequestMemoryAction right) => left.Equals(right); + /// Gets the write value. + public static PermissionPromptRequestPathAccessKind Write { get; } = new("write"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(PermissionPromptRequestPathAccessKind left, PermissionPromptRequestPathAccessKind right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionPromptRequestMemoryAction left, PermissionPromptRequestMemoryAction right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(PermissionPromptRequestPathAccessKind left, PermissionPromptRequestPathAccessKind right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionPromptRequestMemoryAction other && Equals(other); + public override bool Equals(object? obj) => obj is PermissionPromptRequestPathAccessKind other && Equals(other); /// - public bool Equals(PermissionPromptRequestMemoryAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(PermissionPromptRequestPathAccessKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6375,60 +6463,60 @@ public PermissionPromptRequestMemoryAction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionPromptRequestMemoryAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override PermissionPromptRequestPathAccessKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionPromptRequestMemoryAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, PermissionPromptRequestPathAccessKind value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestMemoryAction)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestPathAccessKind)); } } } -/// Vote direction (vote only). +/// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionPromptRequestMemoryDirection : IEquatable +public readonly struct ElicitationRequestedMode : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionPromptRequestMemoryDirection(string value) + public ElicitationRequestedMode(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the upvote value. - public static PermissionPromptRequestMemoryDirection Upvote { get; } = new("upvote"); + /// Gets the form value. + public static ElicitationRequestedMode Form { get; } = new("form"); - /// Gets the downvote value. - public static PermissionPromptRequestMemoryDirection Downvote { get; } = new("downvote"); + /// Gets the url value. + public static ElicitationRequestedMode Url { get; } = new("url"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionPromptRequestMemoryDirection left, PermissionPromptRequestMemoryDirection right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ElicitationRequestedMode left, ElicitationRequestedMode right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionPromptRequestMemoryDirection left, PermissionPromptRequestMemoryDirection right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ElicitationRequestedMode left, ElicitationRequestedMode right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionPromptRequestMemoryDirection other && Equals(other); + public override bool Equals(object? obj) => obj is ElicitationRequestedMode other && Equals(other); /// - public bool Equals(PermissionPromptRequestMemoryDirection other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ElicitationRequestedMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6436,63 +6524,63 @@ public PermissionPromptRequestMemoryDirection(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionPromptRequestMemoryDirection Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ElicitationRequestedMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionPromptRequestMemoryDirection value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ElicitationRequestedMode value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestMemoryDirection)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationRequestedMode)); } } } -/// Underlying permission kind that needs path approval. +/// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct PermissionPromptRequestPathAccessKind : IEquatable +public readonly struct ElicitationCompletedAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public PermissionPromptRequestPathAccessKind(string value) + public ElicitationCompletedAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the read value. - public static PermissionPromptRequestPathAccessKind Read { get; } = new("read"); + /// Gets the accept value. + public static ElicitationCompletedAction Accept { get; } = new("accept"); - /// Gets the shell value. - public static PermissionPromptRequestPathAccessKind Shell { get; } = new("shell"); + /// Gets the decline value. + public static ElicitationCompletedAction Decline { get; } = new("decline"); - /// Gets the write value. - public static PermissionPromptRequestPathAccessKind Write { get; } = new("write"); + /// Gets the cancel value. + public static ElicitationCompletedAction Cancel { get; } = new("cancel"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(PermissionPromptRequestPathAccessKind left, PermissionPromptRequestPathAccessKind right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ElicitationCompletedAction left, ElicitationCompletedAction right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(PermissionPromptRequestPathAccessKind left, PermissionPromptRequestPathAccessKind right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ElicitationCompletedAction left, ElicitationCompletedAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is PermissionPromptRequestPathAccessKind other && Equals(other); + public override bool Equals(object? obj) => obj is ElicitationCompletedAction other && Equals(other); /// - public bool Equals(PermissionPromptRequestPathAccessKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ElicitationCompletedAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6500,60 +6588,63 @@ public PermissionPromptRequestPathAccessKind(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override PermissionPromptRequestPathAccessKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ElicitationCompletedAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, PermissionPromptRequestPathAccessKind value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ElicitationCompletedAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(PermissionPromptRequestPathAccessKind)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationCompletedAction)); } } } -/// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. +/// The user's auto-mode-switch choice. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ElicitationRequestedMode : IEquatable +public readonly struct AutoModeSwitchResponse : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ElicitationRequestedMode(string value) + public AutoModeSwitchResponse(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the form value. - public static ElicitationRequestedMode Form { get; } = new("form"); + /// Gets the yes value. + public static AutoModeSwitchResponse Yes { get; } = new("yes"); - /// Gets the url value. - public static ElicitationRequestedMode Url { get; } = new("url"); + /// Gets the yes_always value. + public static AutoModeSwitchResponse YesAlways { get; } = new("yes_always"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ElicitationRequestedMode left, ElicitationRequestedMode right) => left.Equals(right); + /// Gets the no value. + public static AutoModeSwitchResponse No { get; } = new("no"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ElicitationRequestedMode left, ElicitationRequestedMode right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(AutoModeSwitchResponse left, AutoModeSwitchResponse right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(AutoModeSwitchResponse left, AutoModeSwitchResponse right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ElicitationRequestedMode other && Equals(other); + public override bool Equals(object? obj) => obj is AutoModeSwitchResponse other && Equals(other); /// - public bool Equals(ElicitationRequestedMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(AutoModeSwitchResponse other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6561,63 +6652,66 @@ public ElicitationRequestedMode(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ElicitationRequestedMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override AutoModeSwitchResponse Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ElicitationRequestedMode value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, AutoModeSwitchResponse value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationRequestedMode)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AutoModeSwitchResponse)); } } } -/// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed). +/// Exit plan mode action. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct ElicitationCompletedAction : IEquatable +public readonly struct ExitPlanModeAction : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public ElicitationCompletedAction(string value) + public ExitPlanModeAction(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the accept value. - public static ElicitationCompletedAction Accept { get; } = new("accept"); + /// Gets the exit_only value. + public static ExitPlanModeAction ExitOnly { get; } = new("exit_only"); - /// Gets the decline value. - public static ElicitationCompletedAction Decline { get; } = new("decline"); + /// Gets the interactive value. + public static ExitPlanModeAction Interactive { get; } = new("interactive"); - /// Gets the cancel value. - public static ElicitationCompletedAction Cancel { get; } = new("cancel"); + /// Gets the autopilot value. + public static ExitPlanModeAction Autopilot { get; } = new("autopilot"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(ElicitationCompletedAction left, ElicitationCompletedAction right) => left.Equals(right); + /// Gets the autopilot_fleet value. + public static ExitPlanModeAction AutopilotFleet { get; } = new("autopilot_fleet"); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(ElicitationCompletedAction left, ElicitationCompletedAction right) => !(left == right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(ExitPlanModeAction left, ExitPlanModeAction right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(ExitPlanModeAction left, ExitPlanModeAction right) => !(left == right); /// - public override bool Equals(object? obj) => obj is ElicitationCompletedAction other && Equals(other); + public override bool Equals(object? obj) => obj is ExitPlanModeAction other && Equals(other); /// - public bool Equals(ElicitationCompletedAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(ExitPlanModeAction other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6625,72 +6719,75 @@ public ElicitationCompletedAction(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override ElicitationCompletedAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ExitPlanModeAction Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, ElicitationCompletedAction value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ExitPlanModeAction value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ElicitationCompletedAction)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(ExitPlanModeAction)); } } } -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. +/// Source location type (e.g., project, personal-copilot, plugin, builtin). [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpServersLoadedServerStatus : IEquatable +public readonly struct SkillSource : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpServersLoadedServerStatus(string value) + public SkillSource(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; - /// Gets the connected value. - public static McpServersLoadedServerStatus Connected { get; } = new("connected"); + /// Gets the project value. + public static SkillSource Project { get; } = new("project"); - /// Gets the failed value. - public static McpServersLoadedServerStatus Failed { get; } = new("failed"); + /// Gets the inherited value. + public static SkillSource Inherited { get; } = new("inherited"); - /// Gets the needs-auth value. - public static McpServersLoadedServerStatus NeedsAuth { get; } = new("needs-auth"); + /// Gets the personal-copilot value. + public static SkillSource PersonalCopilot { get; } = new("personal-copilot"); - /// Gets the pending value. - public static McpServersLoadedServerStatus Pending { get; } = new("pending"); + /// Gets the personal-agents value. + public static SkillSource PersonalAgents { get; } = new("personal-agents"); - /// Gets the disabled value. - public static McpServersLoadedServerStatus Disabled { get; } = new("disabled"); + /// Gets the plugin value. + public static SkillSource Plugin { get; } = new("plugin"); - /// Gets the not_configured value. - public static McpServersLoadedServerStatus NotConfigured { get; } = new("not_configured"); + /// Gets the custom value. + public static SkillSource Custom { get; } = new("custom"); + + /// Gets the builtin value. + public static SkillSource Builtin { get; } = new("builtin"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpServersLoadedServerStatus left, McpServersLoadedServerStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(SkillSource left, SkillSource right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpServersLoadedServerStatus left, McpServersLoadedServerStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(SkillSource left, SkillSource right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpServersLoadedServerStatus other && Equals(other); + public override bool Equals(object? obj) => obj is SkillSource other && Equals(other); /// - public bool Equals(McpServersLoadedServerStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(SkillSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6698,72 +6795,139 @@ public McpServersLoadedServerStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpServersLoadedServerStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override SkillSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpServersLoadedServerStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, SkillSource value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServersLoadedServerStatus)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SkillSource)); } } } -/// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured. +/// Configuration source: user, workspace, plugin, or builtin. +[JsonConverter(typeof(Converter))] +[DebuggerDisplay("{Value,nq}")] +public readonly struct McpServerSource : IEquatable +{ + private readonly string? _value; + + /// Initializes a new instance of the struct. + /// The value to associate with this . + [JsonConstructor] + public McpServerSource(string value) + { + ArgumentException.ThrowIfNullOrWhiteSpace(value); + _value = value; + } + + /// Gets the value associated with this . + public string Value => _value ?? string.Empty; + + /// Gets the user value. + public static McpServerSource User { get; } = new("user"); + + /// Gets the workspace value. + public static McpServerSource Workspace { get; } = new("workspace"); + + /// Gets the plugin value. + public static McpServerSource Plugin { get; } = new("plugin"); + + /// Gets the builtin value. + public static McpServerSource Builtin { get; } = new("builtin"); + + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpServerSource left, McpServerSource right) => left.Equals(right); + + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpServerSource left, McpServerSource right) => !(left == right); + + /// + public override bool Equals(object? obj) => obj is McpServerSource other && Equals(other); + + /// + public bool Equals(McpServerSource other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); + + /// + public override string ToString() => Value; + + /// Provides a for serializing instances. + [EditorBrowsable(EditorBrowsableState.Never)] + public sealed class Converter : JsonConverter + { + /// + public override McpServerSource Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); + } + + /// + public override void Write(Utf8JsonWriter writer, McpServerSource value, JsonSerializerOptions options) + { + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerSource)); + } + } +} + +/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured. [JsonConverter(typeof(Converter))] [DebuggerDisplay("{Value,nq}")] -public readonly struct McpServerStatusChangedStatus : IEquatable +public readonly struct McpServerStatus : IEquatable { private readonly string? _value; - /// Initializes a new instance of the struct. - /// The value to associate with this . + /// Initializes a new instance of the struct. + /// The value to associate with this . [JsonConstructor] - public McpServerStatusChangedStatus(string value) + public McpServerStatus(string value) { ArgumentException.ThrowIfNullOrWhiteSpace(value); _value = value; } - /// Gets the value associated with this . + /// Gets the value associated with this . public string Value => _value ?? string.Empty; /// Gets the connected value. - public static McpServerStatusChangedStatus Connected { get; } = new("connected"); + public static McpServerStatus Connected { get; } = new("connected"); /// Gets the failed value. - public static McpServerStatusChangedStatus Failed { get; } = new("failed"); + public static McpServerStatus Failed { get; } = new("failed"); /// Gets the needs-auth value. - public static McpServerStatusChangedStatus NeedsAuth { get; } = new("needs-auth"); + public static McpServerStatus NeedsAuth { get; } = new("needs-auth"); /// Gets the pending value. - public static McpServerStatusChangedStatus Pending { get; } = new("pending"); + public static McpServerStatus Pending { get; } = new("pending"); /// Gets the disabled value. - public static McpServerStatusChangedStatus Disabled { get; } = new("disabled"); + public static McpServerStatus Disabled { get; } = new("disabled"); /// Gets the not_configured value. - public static McpServerStatusChangedStatus NotConfigured { get; } = new("not_configured"); + public static McpServerStatus NotConfigured { get; } = new("not_configured"); - /// Returns a value indicating whether two instances are equivalent. - public static bool operator ==(McpServerStatusChangedStatus left, McpServerStatusChangedStatus right) => left.Equals(right); + /// Returns a value indicating whether two instances are equivalent. + public static bool operator ==(McpServerStatus left, McpServerStatus right) => left.Equals(right); - /// Returns a value indicating whether two instances are not equivalent. - public static bool operator !=(McpServerStatusChangedStatus left, McpServerStatusChangedStatus right) => !(left == right); + /// Returns a value indicating whether two instances are not equivalent. + public static bool operator !=(McpServerStatus left, McpServerStatus right) => !(left == right); /// - public override bool Equals(object? obj) => obj is McpServerStatusChangedStatus other && Equals(other); + public override bool Equals(object? obj) => obj is McpServerStatus other && Equals(other); /// - public bool Equals(McpServerStatusChangedStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); + public bool Equals(McpServerStatus other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase); /// public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value); @@ -6771,20 +6935,20 @@ public McpServerStatusChangedStatus(string value) /// public override string ToString() => Value; - /// Provides a for serializing instances. + /// Provides a for serializing instances. [EditorBrowsable(EditorBrowsableState.Never)] - public sealed class Converter : JsonConverter + public sealed class Converter : JsonConverter { /// - public override McpServerStatusChangedStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override McpServerStatus Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert)); } /// - public override void Write(Utf8JsonWriter writer, McpServerStatusChangedStatus value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, McpServerStatus value, JsonSerializerOptions options) { - GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerStatusChangedStatus)); + GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(McpServerStatus)); } } } diff --git a/dotnet/src/SessionFsProvider.cs b/dotnet/src/SessionFsProvider.cs index 25230244c..a9c627f15 100644 --- a/dotnet/src/SessionFsProvider.cs +++ b/dotnet/src/SessionFsProvider.cs @@ -75,6 +75,24 @@ public abstract class SessionFsProvider : ISessionFsHandler /// Cancellation token. protected abstract Task RenameAsync(string src, string dest, CancellationToken cancellationToken); + /// Executes a SQLite query against the per-session database. + /// Target session identifier. + /// SQL query to execute. + /// How to execute the query. + /// Optional named bind parameters. + /// Cancellation token. + protected abstract Task SqliteQueryAsync( + string sessionId, + string query, + SessionFsSqliteQueryType queryType, + IDictionary? parameters, + CancellationToken cancellationToken); + + /// Checks whether the per-session SQLite database already exists. + /// Target session identifier. + /// Cancellation token. + protected abstract Task SqliteExistsAsync(string sessionId, CancellationToken cancellationToken); + // ---- ISessionFsHandler implementation (private, handles error mapping) ---- async Task ISessionFsHandler.ReadFileAsync(SessionFsReadFileRequest request, CancellationToken cancellationToken) @@ -226,6 +244,45 @@ async Task ISessionFsHandler.ReaddirWithTypesAs } } + async Task ISessionFsHandler.SqliteQueryAsync(SessionFsSqliteQueryRequest request, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(request); + + try + { + return await SqliteQueryAsync(request.SessionId, request.Query, request.QueryType, request.Params, cancellationToken).ConfigureAwait(false); + } + catch (Exception ex) when (!IsCriticalException(ex)) + { + return new SessionFsSqliteQueryResult { Error = ToSessionFsError(ex) }; + } + } + + async Task ISessionFsHandler.SqliteExistsAsync(SessionFsSqliteExistsRequest request, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(request); + + try + { + var exists = await SqliteExistsAsync(request.SessionId, cancellationToken).ConfigureAwait(false); + return new SessionFsSqliteExistsResult { Exists = exists }; + } + catch (Exception ex) when (!IsCriticalException(ex)) + { + return new SessionFsSqliteExistsResult { Exists = false }; + } + } + + private static bool IsCriticalException(Exception ex) => + ex is OperationCanceledException + or OutOfMemoryException + or StackOverflowException + or AccessViolationException + or AppDomainUnloadedException + or BadImageFormatException + or CannotUnloadAppDomainException + or InvalidProgramException; + private static SessionFsError ToSessionFsError(Exception ex) { var code = ex is FileNotFoundException or DirectoryNotFoundException diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index ab38a57bd..42747bcb1 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -798,25 +798,6 @@ public class AutoModeSwitchRequest public double? RetryAfterSeconds { get; set; } } -/// -/// Response to an auto-mode-switch request. -/// -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum AutoModeSwitchResponse -{ - /// Approve the switch for this rate-limit cycle. - [JsonStringEnumMemberName("yes")] - Yes, - - /// Approve and remember the choice for this session. - [JsonStringEnumMemberName("yes_always")] - YesAlways, - - /// Decline the switch. - [JsonStringEnumMemberName("no")] - No -} - /// /// Context for an auto-mode-switch request invocation. /// diff --git a/dotnet/test/E2E/ModeHandlersE2ETests.cs b/dotnet/test/E2E/ModeHandlersE2ETests.cs index 6df3def75..d39bfe02d 100644 --- a/dotnet/test/E2E/ModeHandlersE2ETests.cs +++ b/dotnet/test/E2E/ModeHandlersE2ETests.cs @@ -47,7 +47,7 @@ public async Task Should_Invoke_Exit_Plan_Mode_Handler_When_Model_Uses_Tool() timeoutDescription: "exit_plan_mode.requested event"); var completedEventTask = TestHelper.GetNextEventOfTypeAsync( session, - evt => evt.Data.Approved == true && evt.Data.SelectedAction == "interactive", + evt => evt.Data.Approved == true && evt.Data.SelectedAction.GetValueOrDefault() == ExitPlanModeAction.Interactive, TimeSpan.FromSeconds(30), timeoutDescription: "exit_plan_mode.completed event"); @@ -66,12 +66,18 @@ public async Task Should_Invoke_Exit_Plan_Mode_Handler_When_Model_Uses_Tool() var requestedEvent = await requestedEventTask; Assert.Equal(request.Summary, requestedEvent.Data.Summary); - Assert.Equal(request.Actions, requestedEvent.Data.Actions); - Assert.Equal(request.RecommendedAction, requestedEvent.Data.RecommendedAction); + Assert.Equal(request.Actions, requestedEvent.Data.Actions.Select(action => action.Value)); + Assert.Equal(request.RecommendedAction, requestedEvent.Data.RecommendedAction.Value); var completedEvent = await completedEventTask; Assert.True(completedEvent.Data.Approved); - Assert.Equal("interactive", completedEvent.Data.SelectedAction); + if (completedEvent.Data.SelectedAction is not { } selectedAction) + { + Assert.Fail("Expected a selected action."); + return; + } + + Assert.Equal("interactive", selectedAction.Value); Assert.Equal("Approved by the C# E2E test", completedEvent.Data.Feedback); Assert.NotNull(response); @@ -104,7 +110,7 @@ public async Task Should_Invoke_Auto_Mode_Switch_Handler_When_Rate_Limited() timeoutDescription: "auto_mode_switch.requested event"); var completedEventTask = GetNextEventOfTypeAllowingRateLimitAsync( session, - evt => evt.Data.Response == "yes", + evt => evt.Data.Response == AutoModeSwitchResponse.Yes, TimeSpan.FromSeconds(30), timeoutDescription: "auto_mode_switch.completed event"); var modelChangeTask = GetNextEventOfTypeAllowingRateLimitAsync( @@ -134,7 +140,7 @@ public async Task Should_Invoke_Auto_Mode_Switch_Handler_When_Rate_Limited() Assert.Equal(request.RetryAfterSeconds, requestedEvent.Data.RetryAfterSeconds); var completedEvent = await completedEventTask; - Assert.Equal("yes", completedEvent.Data.Response); + Assert.Equal(AutoModeSwitchResponse.Yes, completedEvent.Data.Response); var modelChange = await modelChangeTask; Assert.Equal("rate_limit_auto_switch", modelChange.Data.Cause); diff --git a/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs b/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs index 821b1be43..a363b5586 100644 --- a/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs +++ b/dotnet/test/E2E/RpcEventSideEffectsE2ETests.cs @@ -28,15 +28,15 @@ public async Task Should_Emit_Mode_Changed_Event_When_Mode_Set() // Subscribe before invoking RPC; events may arrive after the RPC completes. var modeChangedTask = TestHelper.GetNextEventOfTypeAsync( session, - evt => evt.Data.NewMode == "plan" && evt.Data.PreviousMode == "interactive", + evt => evt.Data.NewMode == SessionMode.Plan && evt.Data.PreviousMode == SessionMode.Interactive, EventTimeout, timeoutDescription: "session.mode_changed event for interactive→plan"); await session.Rpc.Mode.SetAsync(SessionMode.Plan); var evt = await modeChangedTask; - Assert.Equal("plan", evt.Data.NewMode); - Assert.Equal("interactive", evt.Data.PreviousMode); + Assert.Equal(SessionMode.Plan, evt.Data.NewMode); + Assert.Equal(SessionMode.Interactive, evt.Data.PreviousMode); } [Fact] diff --git a/dotnet/test/E2E/RpcServerE2ETests.cs b/dotnet/test/E2E/RpcServerE2ETests.cs index aef8a2fbc..847a28c3b 100644 --- a/dotnet/test/E2E/RpcServerE2ETests.cs +++ b/dotnet/test/E2E/RpcServerE2ETests.cs @@ -90,7 +90,7 @@ await ConfigureAuthenticatedUserAsync( Assert.Equal(2, chatQuota.Overage); Assert.True(chatQuota.UsageAllowedWithExhaustedQuota); Assert.True(chatQuota.OverageAllowedWithExhaustedQuota); - Assert.Equal("2026-04-30T00:00:00Z", chatQuota.ResetDate); + Assert.Equal(DateTimeOffset.Parse("2026-04-30T00:00:00Z"), chatQuota.ResetDate); } [Fact] diff --git a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs index 4ccdee858..6e8860964 100644 --- a/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs +++ b/dotnet/test/E2E/RpcTasksAndHandlersE2ETests.cs @@ -101,7 +101,7 @@ await TestHelper.WaitForConditionAsync( Assert.Equal("general-purpose", task.AgentType); Assert.Equal("Reply with TASK_AGENT_DONE exactly.", task.Prompt); Assert.Equal("SDK background agent coverage", task.Description); - Assert.Equal(TaskAgentInfoExecutionMode.Background, task.ExecutionMode); + Assert.Equal(GitHub.Copilot.SDK.Rpc.TaskExecutionMode.Background, task.ExecutionMode); Assert.False(task.CanPromoteToBackground.GetValueOrDefault()); Assert.NotEqual(default, task.StartedAt); @@ -114,8 +114,8 @@ await TestHelper.WaitForConditionAsync( task = await FindAgentTaskAsync(session, started.AgentId); return task?.LatestResponse?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true || task?.Result?.Contains("TASK_AGENT_DONE", StringComparison.Ordinal) == true - || task?.Status == TaskAgentInfoStatus.Completed - || task?.Status == TaskAgentInfoStatus.Failed; + || task?.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Completed + || task?.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Failed; }, timeout: TimeSpan.FromSeconds(60), timeoutMessage: $"Background agent task '{started.AgentId}' did not produce a final observable state."); @@ -123,7 +123,7 @@ await TestHelper.WaitForConditionAsync( Assert.NotNull(task); Assert.Contains("TASK_AGENT_DONE", task.LatestResponse ?? task.Result ?? string.Empty); - if (task.Status == TaskAgentInfoStatus.Idle) + if (task.Status == GitHub.Copilot.SDK.Rpc.TaskStatus.Idle) { var cancel = await session.Rpc.Tasks.CancelAsync(started.AgentId); Assert.True(cancel.Cancelled); diff --git a/dotnet/test/E2E/SessionFsE2ETests.cs b/dotnet/test/E2E/SessionFsE2ETests.cs index 271c7f1e0..c3c317b6f 100644 --- a/dotnet/test/E2E/SessionFsE2ETests.cs +++ b/dotnet/test/E2E/SessionFsE2ETests.cs @@ -294,6 +294,10 @@ public async Task SessionFsProvider_Converts_Exceptions_To_Rpc_Errors() AssertFsError((await handler.ReaddirWithTypesAsync(new SessionFsReaddirWithTypesRequest { Path = "missing-dir" })).Error); AssertFsError(await handler.RmAsync(new SessionFsRmRequest { Path = "missing.txt" })); AssertFsError(await handler.RenameAsync(new SessionFsRenameRequest { Src = "missing.txt", Dest = "dest.txt" })); + AssertFsError((await handler.SqliteQueryAsync(new SessionFsSqliteQueryRequest { Query = "select 1" })).Error); + + var sqliteExists = await handler.SqliteExistsAsync(new SessionFsSqliteExistsRequest()); + Assert.False(sqliteExists.Exists); var unknown = (ISessionFsHandler)new ThrowingSessionFsProvider(new InvalidOperationException("bad path")); var unknownError = await unknown.WriteFileAsync(new SessionFsWriteFileRequest { Path = "bad.txt", Content = "content" }); @@ -603,6 +607,17 @@ protected override Task RmAsync(string path, bool recursive, bool force, Cancell protected override Task RenameAsync(string src, string dest, CancellationToken cancellationToken) => Task.FromException(exception); + + protected override Task SqliteQueryAsync( + string sessionId, + string query, + SessionFsSqliteQueryType queryType, + IDictionary? parameters, + CancellationToken cancellationToken) => + Task.FromException(exception); + + protected override Task SqliteExistsAsync(string sessionId, CancellationToken cancellationToken) => + Task.FromException(exception); } private sealed class TestSessionFsHandler(string sessionId, string rootDir) : SessionFsProvider @@ -736,6 +751,18 @@ protected override Task RenameAsync(string src, string dest, CancellationToken c return Task.CompletedTask; } + protected override Task SqliteQueryAsync( + string sessionId, + string query, + SessionFsSqliteQueryType queryType, + IDictionary? parameters, + CancellationToken cancellationToken) => + Task.FromException( + new NotSupportedException("SQLite session filesystem operations are not supported by this provider.")); + + protected override Task SqliteExistsAsync(string sessionId, CancellationToken cancellationToken) => + Task.FromResult(false); + private string ResolvePath(string sessionPath) { var normalizedSessionId = NormalizeRelativePathSegment(sessionId, nameof(sessionId)); diff --git a/dotnet/test/E2E/SkillsE2ETests.cs b/dotnet/test/E2E/SkillsE2ETests.cs index 6507cb05a..7da1d4b3e 100644 --- a/dotnet/test/E2E/SkillsE2ETests.cs +++ b/dotnet/test/E2E/SkillsE2ETests.cs @@ -134,7 +134,7 @@ public async Task Should_Control_Ambient_Project_Skills_With_EnableConfigDiscove var enabledSkills = await enabledSession.Rpc.Skills.ListAsync(); var discoveredSkill = Assert.Single(enabledSkills.Skills, skill => string.Equals(skill.Name, skillName, StringComparison.Ordinal)); Assert.True(discoveredSkill.Enabled); - Assert.Equal("project", discoveredSkill.Source); + Assert.Equal(SkillSource.Project, discoveredSkill.Source); Assert.EndsWith(Path.Join(skillName, "SKILL.md"), discoveredSkill.Path); await enabledSession.DisposeAsync(); } diff --git a/dotnet/test/Harness/E2ETestContext.cs b/dotnet/test/Harness/E2ETestContext.cs index 6c7d4d808..ef703c4be 100644 --- a/dotnet/test/Harness/E2ETestContext.cs +++ b/dotnet/test/Harness/E2ETestContext.cs @@ -10,6 +10,8 @@ namespace GitHub.Copilot.SDK.Test.Harness; public sealed class E2ETestContext : IAsyncDisposable { + private const string DefaultGitHubToken = "fake-token-for-e2e-tests"; + public string HomeDir { get; } public string WorkDir { get; } public string ProxyUrl { get; } @@ -49,6 +51,11 @@ public static async Task CreateAsync() var proxy = new CapiProxy(); var proxyUrl = await proxy.StartAsync(); + await proxy.SetCopilotUserByTokenAsync(DefaultGitHubToken, new CopilotUserConfig( + Login: "e2e-test-user", + CopilotPlan: "individual_pro", + Endpoints: new CopilotUserEndpoints(Api: proxyUrl, Telemetry: "https://localhost:1/telemetry"), + AnalyticsTrackingId: "e2e-test-tracking-id")); return new E2ETestContext(homeDir, workDir, proxyUrl, proxy, repoRoot); } @@ -190,8 +197,8 @@ public IReadOnlyDictionary GetEnvironment() } if (Environment.GetEnvironmentVariable("GITHUB_ACTIONS") == "true") { - env["GH_TOKEN"] = "fake-token-for-e2e-tests"; - env["GITHUB_TOKEN"] = "fake-token-for-e2e-tests"; + env["GH_TOKEN"] = DefaultGitHubToken; + env["GITHUB_TOKEN"] = DefaultGitHubToken; } return env!; @@ -216,11 +223,10 @@ public CopilotClient CreateClient( } if (autoInjectGitHubToken - && !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("GITHUB_ACTIONS")) && string.IsNullOrEmpty(options.GitHubToken) && string.IsNullOrEmpty(options.CliUrl)) { - options.GitHubToken = "fake-token-for-e2e-tests"; + options.GitHubToken = DefaultGitHubToken; } var client = new CopilotClient(options); diff --git a/dotnet/test/Unit/ForwardCompatibilityTests.cs b/dotnet/test/Unit/ForwardCompatibilityTests.cs index a3b362344..d0265c361 100644 --- a/dotnet/test/Unit/ForwardCompatibilityTests.cs +++ b/dotnet/test/Unit/ForwardCompatibilityTests.cs @@ -240,7 +240,7 @@ public void RpcEnum_WithNonStringValue_ThrowsJsonException() [Fact] public void RpcEnum_DefaultValue_HasEmptyStringValue() { - GitHub.Copilot.SDK.Rpc.SessionMode mode = default; + GitHub.Copilot.SDK.SessionMode mode = default; Assert.Equal(string.Empty, mode.Value); Assert.Equal(string.Empty, mode.ToString()); @@ -249,7 +249,7 @@ public void RpcEnum_DefaultValue_HasEmptyStringValue() [Fact] public void RpcEnum_DefaultValueSerialization_ThrowsJsonException() { - GitHub.Copilot.SDK.Rpc.SessionMode mode = default; + GitHub.Copilot.SDK.SessionMode mode = default; var exception = Assert.Throws(() => JsonSerializer.Serialize( mode, @@ -304,5 +304,5 @@ public void FromJson_UnknownEventType_PreservesAgentIdNull() } } -[JsonSerializable(typeof(GitHub.Copilot.SDK.Rpc.SessionMode))] +[JsonSerializable(typeof(GitHub.Copilot.SDK.SessionMode))] internal partial class ForwardCompatibilityJsonContext : JsonSerializerContext; diff --git a/dotnet/test/Unit/SessionEventSerializationTests.cs b/dotnet/test/Unit/SessionEventSerializationTests.cs index 9299d2f2c..9e2742deb 100644 --- a/dotnet/test/Unit/SessionEventSerializationTests.cs +++ b/dotnet/test/Unit/SessionEventSerializationTests.cs @@ -85,7 +85,7 @@ public class SessionEventSerializationTests { ShutdownType = ShutdownType.Routine, TotalPremiumRequests = 1, - TotalApiDurationMs = 100, + TotalApiDurationMs = TimeSpan.FromMilliseconds(100), SessionStartTime = 1773609948932, CodeChanges = new ShutdownCodeChanges { diff --git a/go/internal/e2e/per_session_auth_e2e_test.go b/go/internal/e2e/per_session_auth_e2e_test.go index 8fa066b73..d40546028 100644 --- a/go/internal/e2e/per_session_auth_e2e_test.go +++ b/go/internal/e2e/per_session_auth_e2e_test.go @@ -1,6 +1,7 @@ package e2e import ( + "strings" "testing" copilot "github.com/github/copilot-sdk/go" @@ -99,7 +100,15 @@ func TestPerSessionAuthE2E(t *testing.T) { t.Run("should be unauthenticated without token", func(t *testing.T) { ctx.ConfigureForTest(t) - session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + noTokenClient := copilot.NewClient(&copilot.ClientOptions{ + CLIPath: ctx.CLIPath, + Cwd: ctx.WorkDir, + Env: withoutAuthEnv(append(ctx.Env(), "COPILOT_DEBUG_GITHUB_API_URL="+ctx.ProxyURL)), + UseLoggedInUser: copilot.Bool(false), + }) + t.Cleanup(func() { noTokenClient.ForceStop() }) + + session, err := noTokenClient.CreateSession(t.Context(), &copilot.SessionConfig{ OnPermissionRequest: copilot.PermissionHandler.ApproveAll, }) if err != nil { @@ -132,3 +141,16 @@ func TestPerSessionAuthE2E(t *testing.T) { t.Logf("Got expected error: %v", err) }) } + +func withoutAuthEnv(env []string) []string { + filtered := make([]string, 0, len(env)+3) + for _, entry := range env { + if strings.HasPrefix(entry, "COPILOT_SDK_AUTH_TOKEN=") || + strings.HasPrefix(entry, "GH_TOKEN=") || + strings.HasPrefix(entry, "GITHUB_TOKEN=") { + continue + } + filtered = append(filtered, entry) + } + return append(filtered, "COPILOT_SDK_AUTH_TOKEN=", "GH_TOKEN=", "GITHUB_TOKEN=") +} diff --git a/go/internal/e2e/rpc_mcp_config_e2e_test.go b/go/internal/e2e/rpc_mcp_config_e2e_test.go index e87e5c91e..528c92080 100644 --- a/go/internal/e2e/rpc_mcp_config_e2e_test.go +++ b/go/internal/e2e/rpc_mcp_config_e2e_test.go @@ -21,11 +21,11 @@ func TestRpcMcpConfigE2E(t *testing.T) { serverName := fmt.Sprintf("sdk-test-%s", randomHex(t)) - baseConfig := &rpc.McpServerConfigLocal{ + baseConfig := &rpc.McpServerConfigStdio{ Command: "node", Args: []string{"-v"}, } - updatedConfig := &rpc.McpServerConfigLocal{ + updatedConfig := &rpc.McpServerConfigStdio{ Command: "node", Args: []string{"--version"}, } @@ -73,7 +73,7 @@ func TestRpcMcpConfigE2E(t *testing.T) { if !present { t.Fatalf("Expected %q to still be present after Update", serverName) } - updatedLocal, ok := updated.(*rpc.McpServerConfigLocal) + updatedLocal, ok := updated.(*rpc.McpServerConfigStdio) if !ok { t.Fatalf("Expected local MCP config, got %T", updated) } diff --git a/go/internal/e2e/rpc_server_e2e_test.go b/go/internal/e2e/rpc_server_e2e_test.go index 35a71e24e..7b83ca586 100644 --- a/go/internal/e2e/rpc_server_e2e_test.go +++ b/go/internal/e2e/rpc_server_e2e_test.go @@ -5,6 +5,7 @@ import ( "path/filepath" "strings" "testing" + "time" copilot "github.com/github/copilot-sdk/go" "github.com/github/copilot-sdk/go/internal/e2e/testharness" @@ -117,7 +118,11 @@ func TestRpcServerE2E(t *testing.T) { if !chat.OverageAllowedWithExhaustedQuota { t.Errorf("Expected OverageAllowedWithExhaustedQuota=true") } - if chat.ResetDate == nil || *chat.ResetDate != "2026-04-30T00:00:00Z" { + expectedResetDate, err := time.Parse(time.RFC3339, "2026-04-30T00:00:00Z") + if err != nil { + t.Fatalf("Parse expected reset date: %v", err) + } + if chat.ResetDate == nil || !chat.ResetDate.Equal(expectedResetDate) { t.Errorf("Expected ResetDate='2026-04-30T00:00:00Z', got %v", chat.ResetDate) } }) diff --git a/go/internal/e2e/session_fs_e2e_test.go b/go/internal/e2e/session_fs_e2e_test.go index d56dc14a3..a23613c72 100644 --- a/go/internal/e2e/session_fs_e2e_test.go +++ b/go/internal/e2e/session_fs_e2e_test.go @@ -473,6 +473,23 @@ func (h *testSessionFsHandler) Rename(src string, dest string) error { return os.Rename(providerPath(h.root, h.sessionID, src), destPath) } +func (h *testSessionFsHandler) SqliteQuery(sessionID string, query string, queryType rpc.SessionFsSqliteQueryType, params map[string]any) (*rpc.SessionFsSqliteQueryResult, error) { + return &rpc.SessionFsSqliteQueryResult{ + Columns: []string{"sessionId", "query", "queryType", "answer"}, + Rows: []map[string]any{{ + "sessionId": sessionID, + "query": query, + "queryType": string(queryType), + "answer": params["answer"], + }}, + RowsAffected: 0, + }, nil +} + +func (h *testSessionFsHandler) SqliteExists(sessionID string) (bool, error) { + return sessionID == h.sessionID, nil +} + func providerPath(root string, sessionID string, path string) string { trimmed := strings.TrimPrefix(path, "/") if trimmed == "" { @@ -636,6 +653,28 @@ func TestSessionFsHandlerOperationsE2E(t *testing.T) { if _, err := handler.Stat("/workspace/nested/missing.txt"); err == nil || !os.IsNotExist(err) { t.Errorf("Expected os.ErrNotExist from Stat on missing file, got %v", err) } + + sqliteResult, err := handler.SqliteQuery(sessionID, "select :answer as answer", rpc.SessionFsSqliteQueryTypeQuery, map[string]any{"answer": 42}) + if err != nil { + t.Fatalf("SqliteQuery failed: %v", err) + } + if len(sqliteResult.Columns) != 4 || sqliteResult.Columns[3] != "answer" { + t.Errorf("Expected SQLite result columns to include answer, got %v", sqliteResult.Columns) + } + if len(sqliteResult.Rows) != 1 || sqliteResult.Rows[0]["answer"] != 42 { + t.Errorf("Expected SQLite result row to include answer=42, got %+v", sqliteResult.Rows) + } + if sqliteResult.RowsAffected != 0 { + t.Errorf("Expected RowsAffected=0, got %d", sqliteResult.RowsAffected) + } + + sqliteExists, err := handler.SqliteExists(sessionID) + if err != nil { + t.Fatalf("SqliteExists failed: %v", err) + } + if !sqliteExists { + t.Error("Expected SQLite database to exist for the handler session") + } } func sliceContains(slice []string, value string) bool { diff --git a/go/internal/e2e/testharness/context.go b/go/internal/e2e/testharness/context.go index e8efda82f..d7d30e090 100644 --- a/go/internal/e2e/testharness/context.go +++ b/go/internal/e2e/testharness/context.go @@ -12,6 +12,8 @@ import ( copilot "github.com/github/copilot-sdk/go" ) +const defaultGitHubToken = "fake-token-for-e2e-tests" + var ( cliPath string cliPathOnce sync.Once @@ -81,6 +83,20 @@ func NewTestContext(t *testing.T) *TestContext { os.RemoveAll(workDir) t.Fatalf("Failed to start proxy: %v", err) } + if err := proxy.SetCopilotUserByToken(defaultGitHubToken, map[string]interface{}{ + "login": "e2e-test-user", + "copilot_plan": "individual_pro", + "endpoints": map[string]interface{}{ + "api": proxyURL, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "e2e-test-tracking-id", + }); err != nil { + proxy.StopWithOptions(true) + os.RemoveAll(homeDir) + os.RemoveAll(workDir) + t.Fatalf("Failed to configure default Copilot user: %v", err) + } ctx := &TestContext{ CLIPath: cliPath, @@ -167,16 +183,13 @@ func (c *TestContext) Env() []string { env = append(env, "COPILOT_API_URL="+c.ProxyURL, "COPILOT_HOME="+c.HomeDir, + "COPILOT_SDK_AUTH_TOKEN="+defaultGitHubToken, "GH_CONFIG_DIR="+c.HomeDir, + "GH_TOKEN="+defaultGitHubToken, + "GITHUB_TOKEN="+defaultGitHubToken, "XDG_CONFIG_HOME="+c.HomeDir, "XDG_STATE_HOME="+c.HomeDir, ) - if os.Getenv("GITHUB_ACTIONS") == "true" { - env = append(env, - "GH_TOKEN=fake-token-for-e2e-tests", - "GITHUB_TOKEN=fake-token-for-e2e-tests", - ) - } return env } @@ -193,9 +206,8 @@ func (c *TestContext) NewClient(opts ...func(*copilot.ClientOptions)) *copilot.C opt(options) } - // Use fake token in CI to allow cached responses without real auth for spawned subprocess clients. - if os.Getenv("GITHUB_ACTIONS") == "true" && options.GitHubToken == "" && options.CLIUrl == "" { - options.GitHubToken = "fake-token-for-e2e-tests" + if options.GitHubToken == "" && options.CLIUrl == "" { + options.GitHubToken = defaultGitHubToken } return copilot.NewClient(options) diff --git a/go/rpc/generated_rpc_api_shape_test.go b/go/rpc/generated_rpc_api_shape_test.go index 496630c55..bddbb263d 100644 --- a/go/rpc/generated_rpc_api_shape_test.go +++ b/go/rpc/generated_rpc_api_shape_test.go @@ -15,9 +15,9 @@ var ( _ ExternalToolResult = ExternalToolStringResult("") _ ExternalToolResult = (*ExternalToolTextResultForLlm)(nil) _ FilterMapping = FilterMappingEnumMap{} - _ FilterMapping = FilterMappingStringMarkdown + _ FilterMapping = ContentFilterModeMarkdown _ McpServerConfig = (*McpServerConfigHTTP)(nil) - _ McpServerConfig = (*McpServerConfigLocal)(nil) + _ McpServerConfig = (*McpServerConfigStdio)(nil) _ UIElicitationFieldValue = UIElicitationStringValue("") _ UIElicitationFieldValue = UIElicitationStringArrayValue(nil) _ UIElicitationFieldValue = UIElicitationBooleanValue(false) @@ -32,14 +32,14 @@ func TestGeneratedRPCAPIShape(t *testing.T) { assertStructFieldType(t, file, fileSet, "HandlePendingToolCallRequest", "Result", "ExternalToolResult") assertInterfaceType(t, file, "FilterMapping") - assertTypeExpr(t, fileSet, findTypeSpec(t, file, "FilterMappingEnumMap").Type, "map[string]FilterMappingValue") + assertTypeExpr(t, fileSet, findTypeSpec(t, file, "FilterMappingEnumMap").Type, "map[string]ContentFilterMode") assertInterfaceType(t, file, "McpServerConfig") assertStructFieldType(t, file, fileSet, "McpConfigAddRequest", "Config", "McpServerConfig") assertStructFieldType(t, file, fileSet, "McpConfigList", "Servers", "map[string]McpServerConfig") assertStructFieldType(t, file, fileSet, "McpConfigUpdateRequest", "Config", "McpServerConfig") assertStructFieldType(t, file, fileSet, "McpServerConfigHTTP", "FilterMapping", "FilterMapping") - assertStructFieldType(t, file, fileSet, "McpServerConfigLocal", "FilterMapping", "FilterMapping") + assertStructFieldType(t, file, fileSet, "McpServerConfigStdio", "FilterMapping", "FilterMapping") assertInterfaceType(t, file, "UIElicitationFieldValue") assertTypeExpr(t, fileSet, findTypeSpec(t, file, "UIElicitationStringArrayValue").Type, "[]string") diff --git a/go/rpc/generated_rpc_union_test.go b/go/rpc/generated_rpc_union_test.go index b33141926..8a85ee398 100644 --- a/go/rpc/generated_rpc_union_test.go +++ b/go/rpc/generated_rpc_union_test.go @@ -47,7 +47,7 @@ func TestExternalToolResultJSONUnion(t *testing.T) { } func TestFilterMappingJSONUnion(t *testing.T) { - var mapping FilterMapping = FilterMappingEnumMap{"secret": FilterMappingValueHiddenCharacters} + var mapping FilterMapping = FilterMappingEnumMap{"secret": ContentFilterModeHiddenCharacters} raw, err := json.Marshal(mapping) if err != nil { t.Fatalf("marshal filter mapping map: %v", err) @@ -61,11 +61,11 @@ func TestFilterMappingJSONUnion(t *testing.T) { t.Fatalf("unmarshal filter mapping map: %v", err) } decodedMapValue, ok := decodedMap.(FilterMappingEnumMap) - if !ok || decodedMapValue["secret"] != FilterMappingValueHiddenCharacters { + if !ok || decodedMapValue["secret"] != ContentFilterModeHiddenCharacters { t.Fatalf("unmarshal filter mapping map = %#v", decodedMap) } - var enumValue FilterMapping = FilterMappingStringMarkdown + var enumValue FilterMapping = ContentFilterModeMarkdown raw, err = json.Marshal(enumValue) if err != nil { t.Fatalf("marshal filter mapping enum: %v", err) @@ -78,14 +78,14 @@ func TestFilterMappingJSONUnion(t *testing.T) { if err != nil { t.Fatalf("unmarshal filter mapping enum: %v", err) } - decodedEnumValue, ok := decodedEnum.(FilterMappingString) - if !ok || decodedEnumValue != FilterMappingStringMarkdown { + decodedEnumValue, ok := decodedEnum.(ContentFilterMode) + if !ok || decodedEnumValue != ContentFilterModeMarkdown { t.Fatalf("unmarshal filter mapping enum = %#v", decodedEnum) } } func TestMcpServerConfigJSONUnion(t *testing.T) { - var localConfig McpServerConfig = &McpServerConfigLocal{ + var localConfig McpServerConfig = &McpServerConfigStdio{ Args: []string{"-v"}, Command: "node", } @@ -101,7 +101,7 @@ func TestMcpServerConfigJSONUnion(t *testing.T) { if err != nil { t.Fatalf("unmarshal local config: %v", err) } - decodedLocalValue, ok := decodedLocal.(*McpServerConfigLocal) + decodedLocalValue, ok := decodedLocal.(*McpServerConfigStdio) if !ok || decodedLocalValue.Command != "node" || len(decodedLocalValue.Args) != 1 || decodedLocalValue.Args[0] != "-v" { t.Fatalf("unmarshal local config = %#v", decodedLocal) } diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go index 896004b2a..ac04cae44 100644 --- a/go/rpc/zrpc.go +++ b/go/rpc/zrpc.go @@ -26,7 +26,7 @@ type AccountGetQuotaResult struct { // Schema for the `AccountQuotaSnapshot` type. type AccountQuotaSnapshot struct { - // Number of requests included in the entitlement + // Number of requests included in the entitlement, or -1 for unlimited entitlements EntitlementRequests int64 `json:"entitlementRequests"` // Whether the user has an unlimited usage entitlement IsUnlimitedEntitlement bool `json:"isUnlimitedEntitlement"` @@ -37,7 +37,7 @@ type AccountQuotaSnapshot struct { // Percentage of entitlement remaining RemainingPercentage float64 `json:"remainingPercentage"` // Date when the quota resets (ISO 8601 string) - ResetDate *string `json:"resetDate,omitempty"` + ResetDate *time.Time `json:"resetDate,omitempty"` // Whether usage is still permitted after quota exhaustion UsageAllowedWithExhaustedQuota bool `json:"usageAllowedWithExhaustedQuota"` // Number of requests used so far this period @@ -157,7 +157,7 @@ type ConnectedRemoteSessionMetadata struct { // Neutral SDK discriminator for the connected remote session kind. Kind ConnectedRemoteSessionMetadataKind `json:"kind"` // Last session update time as an ISO 8601 string. - ModifiedTime string `json:"modifiedTime"` + ModifiedTime time.Time `json:"modifiedTime"` // Optional friendly session name. Name *string `json:"name,omitempty"` // Pull request number associated with the session. @@ -169,9 +169,9 @@ type ConnectedRemoteSessionMetadata struct { // SDK session ID for the connected remote session. SessionID string `json:"sessionId"` // Remote session staleness deadline as an ISO 8601 string. - StaleAt *string `json:"staleAt,omitempty"` + StaleAt *time.Time `json:"staleAt,omitempty"` // Session start time as an ISO 8601 string. - StartTime string `json:"startTime"` + StartTime time.Time `json:"startTime"` // Remote session state returned by the backing service. State *string `json:"state,omitempty"` // Optional session summary. @@ -228,9 +228,9 @@ type DiscoveredMcpServer struct { Enabled bool `json:"enabled"` // Server name (config key) Name string `json:"name"` - // Configuration source - Source DiscoveredMcpServerSource `json:"source"` - // Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + // Configuration source: user, workspace, plugin, or builtin + Source McpServerSource `json:"source"` + // Server transport type: stdio, http, sse, or memory Type *DiscoveredMcpServerType `json:"type,omitempty"` } @@ -285,6 +285,8 @@ func (ExternalToolTextResultForLlm) externalToolResult() {} // Expanded external tool result payload type ExternalToolTextResultForLlm struct { + // Base64-encoded binary results returned to the model + BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitempty"` // Structured content blocks from the tool Contents []ExternalToolTextResultForLlmContent `json:"contents,omitempty"` // Optional error message for failed executions @@ -300,6 +302,19 @@ type ExternalToolTextResultForLlm struct { ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } +// Binary result returned by a tool for the model +type ExternalToolTextResultForLlmBinaryResultsForLlm struct { + // Base64-encoded binary data + Data string `json:"data"` + // Human-readable description of the binary data + Description *string `json:"description,omitempty"` + // MIME type of the binary data + MIMEType string `json:"mimeType"` + // Binary result type discriminator. Use "image" for images and "resource" for other binary + // data. + Type ExternalToolTextResultForLlmBinaryResultsForLlmType `json:"type"` +} + // A content block within a tool result, which may be text, terminal output, image, audio, // or a resource type ExternalToolTextResultForLlmContent interface { @@ -457,11 +472,11 @@ type FilterMapping interface { filterMapping() } -type FilterMappingEnumMap map[string]FilterMappingValue +func (ContentFilterMode) filterMapping() {} -func (FilterMappingEnumMap) filterMapping() {} +type FilterMappingEnumMap map[string]ContentFilterMode -func (FilterMappingString) filterMapping() {} +func (FilterMappingEnumMap) filterMapping() {} // Optional user prompt to combine with the fleet orchestration instructions. // Experimental: FleetStartRequest is part of an experimental API and may change or be @@ -592,7 +607,7 @@ type LogResult struct { // MCP server name and configuration to add to user configuration. type McpConfigAddRequest struct { - // MCP server configuration (local/stdio or remote/http) + // MCP server configuration (stdio process or remote HTTP/SSE) Config McpServerConfig `json:"config"` // Unique name for the MCP server Name string `json:"name"` @@ -639,7 +654,7 @@ type McpConfigRemoveResult struct { // MCP server name and replacement configuration to write to user configuration. type McpConfigUpdateRequest struct { - // MCP server configuration (local/stdio or remote/http) + // MCP server configuration (stdio process or remote HTTP/SSE) Config McpServerConfig `json:"config"` // Name of the MCP server to update Name string `json:"name"` @@ -725,7 +740,7 @@ type McpServer struct { Status McpServerStatus `json:"status"` } -// MCP server configuration (local/stdio or remote/http) +// MCP server configuration (stdio process or remote HTTP/SSE) type McpServerConfig interface { mcpServerConfig() } @@ -738,6 +753,8 @@ func (RawMcpServerConfigData) mcpServerConfig() {} // Remote MCP server configuration accessed over HTTP or SSE. type McpServerConfigHTTP struct { + // Additional authentication configuration for this server. + Auth *McpServerConfigHTTPAuth `json:"auth,omitempty"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. FilterMapping FilterMapping `json:"filterMapping,omitempty"` @@ -764,15 +781,15 @@ type McpServerConfigHTTP struct { func (McpServerConfigHTTP) mcpServerConfig() {} -// Local MCP server configuration launched as a child process. -type McpServerConfigLocal struct { - // Command-line arguments passed to the local MCP server process. - Args []string `json:"args"` - // Executable command used to start the local MCP server process. +// Stdio MCP server configuration launched as a child process. +type McpServerConfigStdio struct { + // Command-line arguments passed to the Stdio MCP server process. + Args []string `json:"args,omitempty"` + // Executable command used to start the Stdio MCP server process. Command string `json:"command"` - // Working directory for the local MCP server process. + // Working directory for the Stdio MCP server process. Cwd *string `json:"cwd,omitempty"` - // Environment variables to pass to the local MCP server process. + // Environment variables to pass to the Stdio MCP server process. Env map[string]string `json:"env,omitempty"` // Content filtering mode to apply to all tools, or a map of tool name to content filtering // mode. @@ -784,11 +801,15 @@ type McpServerConfigLocal struct { Timeout *int64 `json:"timeout,omitempty"` // Tools to include. Defaults to all tools if not specified. Tools []string `json:"tools,omitempty"` - // Local transport type. Defaults to "local". - Type *McpServerConfigLocalType `json:"type,omitempty"` } -func (McpServerConfigLocal) mcpServerConfig() {} +func (McpServerConfigStdio) mcpServerConfig() {} + +// Additional authentication configuration for this server. +type McpServerConfigHTTPAuth struct { + // Fixed port for the OAuth redirect callback server. + RedirectPort *int32 `json:"redirectPort,omitempty"` +} // MCP servers configured for the session, with their connection status. // Experimental: McpServerList is part of an experimental API and may change or be removed. @@ -928,7 +949,7 @@ type ModelList struct { // Policy state (if applicable) type ModelPolicy struct { // Current policy state for this model - State string `json:"state"` + State ModelPolicyState `json:"state"` // Usage terms or conditions for this model Terms *string `json:"terms,omitempty"` } @@ -959,7 +980,7 @@ type ModelSwitchToResult struct { // Agent interaction mode to apply to the session. type ModeSetRequest struct { - // The agent mode. Valid values: "interactive", "plan", "autopilot". + // The session mode the agent is operating in Mode SessionMode `json:"mode"` } @@ -1461,7 +1482,7 @@ type ServerSkill struct { // 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"` + Source SkillSource `json:"source"` // Whether the skill can be invoked by the user as a slash command UserInvocable bool `json:"userInvocable"` } @@ -1638,9 +1659,17 @@ type SessionFsRmRequest struct { SessionID string `json:"sessionId"` } +// Optional capabilities declared by the provider +type SessionFsSetProviderCapabilities struct { + // Whether the provider supports SQLite query/exists operations + Sqlite *bool `json:"sqlite,omitempty"` +} + // Initial working directory, session-state path layout, and path conventions used to // register the calling SDK client as the session filesystem provider. type SessionFsSetProviderRequest struct { + // Optional capabilities declared by the provider + Capabilities *SessionFsSetProviderCapabilities `json:"capabilities,omitempty"` // Path conventions used by this filesystem Conventions SessionFsSetProviderConventions `json:"conventions"` // Initial working directory for sessions @@ -1655,6 +1684,47 @@ type SessionFsSetProviderResult struct { Success bool `json:"success"` } +// Identifies the target session. +type SessionFsSqliteExistsRequest struct { + // Target session identifier + SessionID string `json:"sessionId"` +} + +// Indicates whether the per-session SQLite database already exists. +type SessionFsSqliteExistsResult struct { + // Whether the session database already exists + Exists bool `json:"exists"` +} + +// SQL query, query type, and optional bind parameters for executing a SQLite query against +// the per-session database. +type SessionFsSqliteQueryRequest struct { + // Optional named bind parameters + Params map[string]any `json:"params,omitempty"` + // SQL query to execute + Query string `json:"query"` + // How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT + // (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + QueryType SessionFsSqliteQueryType `json:"queryType"` + // Target session identifier + SessionID string `json:"sessionId"` +} + +// Query results including rows, columns, and rows affected, or a filesystem error if +// execution failed. +type SessionFsSqliteQueryResult struct { + // Column names from the result set + Columns []string `json:"columns"` + // Describes a filesystem error. + Error *SessionFsError `json:"error,omitempty"` + // Last inserted row ID (for INSERT) + LastInsertRowid *float64 `json:"lastInsertRowid,omitempty"` + // For SELECT: array of row objects. For others: empty array. + Rows []map[string]any `json:"rows"` + // Number of rows affected (for INSERT/UPDATE/DELETE) + RowsAffected int64 `json:"rowsAffected"` +} + // Path whose metadata should be returned from the client-provided session filesystem. type SessionFsStatRequest struct { // Path using SessionFs conventions @@ -1806,8 +1876,8 @@ type Skill struct { Name string `json:"name"` // Absolute path to the skill file Path *string `json:"path,omitempty"` - // Source location type (e.g., project, personal, plugin) - Source string `json:"source"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source SkillSource `json:"source"` // Whether the skill can be invoked by the user as a slash command UserInvocable bool `json:"userInvocable"` } @@ -1916,8 +1986,8 @@ func (r RawSlashCommandInvocationResultData) Kind() SlashCommandInvocationResult type SlashCommandAgentPromptResult struct { // Prompt text to display to the user DisplayPrompt string `json:"displayPrompt"` - // Optional target session mode - Mode *SlashCommandAgentPromptMode `json:"mode,omitempty"` + // Optional target session mode for the agent prompt + Mode *SessionMode `json:"mode,omitempty"` // Prompt to submit to the agent Prompt string `json:"prompt"` // True when the invocation mutated user runtime settings; consumers caching settings should @@ -1998,8 +2068,8 @@ type TaskAgentInfo struct { Description string `json:"description"` // Error message when the task failed Error *string `json:"error,omitempty"` - // How the agent is currently being managed by the runtime - ExecutionMode *TaskAgentInfoExecutionMode `json:"executionMode,omitempty"` + // Whether task execution is synchronously awaited or managed in the background + ExecutionMode *TaskExecutionMode `json:"executionMode,omitempty"` // Unique task identifier ID string `json:"id"` // ISO 8601 timestamp when the agent entered idle state @@ -2015,7 +2085,7 @@ type TaskAgentInfo struct { // ISO 8601 timestamp when the task was started StartedAt time.Time `json:"startedAt"` // Current lifecycle status of the task - Status TaskAgentInfoStatus `json:"status"` + Status TaskStatus `json:"status"` // Tool call ID associated with this agent task ToolCallID string `json:"toolCallId"` } @@ -2039,8 +2109,8 @@ type TaskShellInfo struct { CompletedAt *time.Time `json:"completedAt,omitempty"` // Short description of the task Description string `json:"description"` - // Whether the shell command is currently sync-waited or background-managed - ExecutionMode *TaskShellInfoExecutionMode `json:"executionMode,omitempty"` + // Whether task execution is synchronously awaited or managed in the background + ExecutionMode *TaskExecutionMode `json:"executionMode,omitempty"` // Unique task identifier ID string `json:"id"` // Path to the detached shell log, when available @@ -2050,7 +2120,7 @@ type TaskShellInfo struct { // ISO 8601 timestamp when the task was started StartedAt time.Time `json:"startedAt"` // Current lifecycle status of the task - Status TaskShellInfoStatus `json:"status"` + Status TaskStatus `json:"status"` } func (TaskShellInfo) taskInfo() {} @@ -2611,17 +2681,18 @@ const ( ConnectedRemoteSessionMetadataKindRemoteSession ConnectedRemoteSessionMetadataKind = "remote-session" ) -// Configuration source -type DiscoveredMcpServerSource string +// Controls how MCP tool result content is filtered: none leaves content unchanged, markdown +// sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes +// characters that can hide directives. +type ContentFilterMode string const ( - DiscoveredMcpServerSourceBuiltin DiscoveredMcpServerSource = "builtin" - DiscoveredMcpServerSourcePlugin DiscoveredMcpServerSource = "plugin" - DiscoveredMcpServerSourceUser DiscoveredMcpServerSource = "user" - DiscoveredMcpServerSourceWorkspace DiscoveredMcpServerSource = "workspace" + ContentFilterModeHiddenCharacters ContentFilterMode = "hidden_characters" + ContentFilterModeMarkdown ContentFilterMode = "markdown" + ContentFilterModeNone ContentFilterMode = "none" ) -// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) +// Server transport type: stdio, http, sse, or memory type DiscoveredMcpServerType string const ( @@ -2651,6 +2722,15 @@ const ( ExtensionStatusStarting ExtensionStatus = "starting" ) +// Binary result type discriminator. Use "image" for images and "resource" for other binary +// data. +type ExternalToolTextResultForLlmBinaryResultsForLlmType string + +const ( + ExternalToolTextResultForLlmBinaryResultsForLlmTypeImage ExternalToolTextResultForLlmBinaryResultsForLlmType = "image" + ExternalToolTextResultForLlmBinaryResultsForLlmTypeResource ExternalToolTextResultForLlmBinaryResultsForLlmType = "resource" +) + // Theme variant this icon is intended for type ExternalToolTextResultForLlmContentResourceLinkIconTheme string @@ -2671,24 +2751,6 @@ const ( ExternalToolTextResultForLlmContentTypeText ExternalToolTextResultForLlmContentType = "text" ) -// Allowed values for the `FilterMappingString` enumeration. -type FilterMappingString string - -const ( - FilterMappingStringHiddenCharacters FilterMappingString = "hidden_characters" - FilterMappingStringMarkdown FilterMappingString = "markdown" - FilterMappingStringNone FilterMappingString = "none" -) - -// Allowed values for the `FilterMappingValue` enumeration. -type FilterMappingValue string - -const ( - FilterMappingValueHiddenCharacters FilterMappingValue = "hidden_characters" - FilterMappingValueMarkdown FilterMappingValue = "markdown" - FilterMappingValueNone FilterMappingValue = "none" -) - // Where this source lives — used for UI grouping type InstructionsSourcesLocation string @@ -2726,16 +2788,7 @@ const ( McpServerConfigHTTPTypeSse McpServerConfigHTTPType = "sse" ) -// Local transport type. Defaults to "local". -type McpServerConfigLocalType string - -const ( - McpServerConfigLocalTypeLocal McpServerConfigLocalType = "local" - McpServerConfigLocalTypeStdio McpServerConfigLocalType = "stdio" -) - // Configuration source: user, workspace, plugin, or builtin -// Experimental: McpServerSource is part of an experimental API and may change or be removed. type McpServerSource string const ( @@ -2777,6 +2830,15 @@ const ( ModelPickerPriceCategoryVeryHigh ModelPickerPriceCategory = "very_high" ) +// Current policy state for this model +type ModelPolicyState string + +const ( + ModelPolicyStateDisabled ModelPolicyState = "disabled" + ModelPolicyStateEnabled ModelPolicyState = "enabled" + ModelPolicyStateUnconfigured ModelPolicyState = "unconfigured" +) + // Kind discriminator for PermissionDecisionApproveForLocationApproval. type PermissionDecisionApproveForLocationApprovalKind string @@ -2864,6 +2926,16 @@ const ( SessionFsSetProviderConventionsWindows SessionFsSetProviderConventions = "windows" ) +// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT +// (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) +type SessionFsSqliteQueryType string + +const ( + SessionFsSqliteQueryTypeExec SessionFsSqliteQueryType = "exec" + SessionFsSqliteQueryTypeQuery SessionFsSqliteQueryType = "query" + SessionFsSqliteQueryTypeRun SessionFsSqliteQueryType = "run" +) + // Log severity level. Determines how the message is displayed in the timeline. Defaults to // "info". type SessionLogLevel string @@ -2874,7 +2946,7 @@ const ( SessionLogLevelWarning SessionLogLevel = "warning" ) -// The agent mode. Valid values: "interactive", "plan", "autopilot". +// The session mode the agent is operating in type SessionMode string const ( @@ -2892,13 +2964,17 @@ const ( ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM" ) -// Optional target session mode -type SlashCommandAgentPromptMode string +// Source location type (e.g., project, personal-copilot, plugin, builtin) +type SkillSource string const ( - SlashCommandAgentPromptModeAutopilot SlashCommandAgentPromptMode = "autopilot" - SlashCommandAgentPromptModeInteractive SlashCommandAgentPromptMode = "interactive" - SlashCommandAgentPromptModePlan SlashCommandAgentPromptMode = "plan" + SkillSourceBuiltin SkillSource = "builtin" + SkillSourceCustom SkillSource = "custom" + SkillSourceInherited SkillSource = "inherited" + SkillSourcePersonalAgents SkillSource = "personal-agents" + SkillSourcePersonalCopilot SkillSource = "personal-copilot" + SkillSourcePlugin SkillSource = "plugin" + SkillSourceProject SkillSource = "project" ) // Optional completion hint for the input (e.g. 'directory' for filesystem path completion) @@ -2927,27 +3003,14 @@ const ( SlashCommandKindSkill SlashCommandKind = "skill" ) -// How the agent is currently being managed by the runtime -// Experimental: TaskAgentInfoExecutionMode is part of an experimental API and may change or -// be removed. -type TaskAgentInfoExecutionMode string - -const ( - TaskAgentInfoExecutionModeBackground TaskAgentInfoExecutionMode = "background" - TaskAgentInfoExecutionModeSync TaskAgentInfoExecutionMode = "sync" -) - -// Current lifecycle status of the task -// Experimental: TaskAgentInfoStatus is part of an experimental API and may change or be +// Whether task execution is synchronously awaited or managed in the background +// Experimental: TaskExecutionMode is part of an experimental API and may change or be // removed. -type TaskAgentInfoStatus string +type TaskExecutionMode string const ( - TaskAgentInfoStatusCancelled TaskAgentInfoStatus = "cancelled" - TaskAgentInfoStatusCompleted TaskAgentInfoStatus = "completed" - TaskAgentInfoStatusFailed TaskAgentInfoStatus = "failed" - TaskAgentInfoStatusIdle TaskAgentInfoStatus = "idle" - TaskAgentInfoStatusRunning TaskAgentInfoStatus = "running" + TaskExecutionModeBackground TaskExecutionMode = "background" + TaskExecutionModeSync TaskExecutionMode = "sync" ) // Type discriminator for TaskInfo. @@ -2969,27 +3032,16 @@ const ( TaskShellInfoAttachmentModeDetached TaskShellInfoAttachmentMode = "detached" ) -// Whether the shell command is currently sync-waited or background-managed -// Experimental: TaskShellInfoExecutionMode is part of an experimental API and may change or -// be removed. -type TaskShellInfoExecutionMode string - -const ( - TaskShellInfoExecutionModeBackground TaskShellInfoExecutionMode = "background" - TaskShellInfoExecutionModeSync TaskShellInfoExecutionMode = "sync" -) - // Current lifecycle status of the task -// Experimental: TaskShellInfoStatus is part of an experimental API and may change or be -// removed. -type TaskShellInfoStatus string +// Experimental: TaskStatus is part of an experimental API and may change or be removed. +type TaskStatus string const ( - TaskShellInfoStatusCancelled TaskShellInfoStatus = "cancelled" - TaskShellInfoStatusCompleted TaskShellInfoStatus = "completed" - TaskShellInfoStatusFailed TaskShellInfoStatus = "failed" - TaskShellInfoStatusIdle TaskShellInfoStatus = "idle" - TaskShellInfoStatusRunning TaskShellInfoStatus = "running" + TaskStatusCancelled TaskStatus = "cancelled" + TaskStatusCompleted TaskStatus = "completed" + TaskStatusFailed TaskStatus = "failed" + TaskStatusIdle TaskStatus = "idle" + TaskStatusRunning TaskStatus = "running" ) // Type discriminator. Always "string". @@ -3987,7 +4039,7 @@ type ModeApi sessionApi // // RPC method: session.mode.get. // -// Returns: The agent mode. Valid values: "interactive", "plan", "autopilot". +// Returns: The session mode the agent is operating in func (a *ModeApi) Get(ctx context.Context) (*SessionMode, error) { req := map[string]any{"sessionId": a.sessionID} raw, err := a.client.Request("session.mode.get", req) @@ -4978,6 +5030,25 @@ type SessionFsHandler interface { // // Returns: Describes a filesystem error. Rm(request *SessionFsRmRequest) (*SessionFsError, error) + // SqliteExists checks whether the per-session SQLite database already exists, without + // creating it. + // + // RPC method: sessionFs.sqliteExists. + // + // Parameters: Identifies the target session. + // + // Returns: Indicates whether the per-session SQLite database already exists. + SqliteExists(request *SessionFsSqliteExistsRequest) (*SessionFsSqliteExistsResult, error) + // SqliteQuery executes a SQLite query against the per-session database. + // + // RPC method: sessionFs.sqliteQuery. + // + // Parameters: SQL query, query type, and optional bind parameters for executing a SQLite + // query against the per-session database. + // + // Returns: Query results including rows, columns, and rows affected, or a filesystem error + // if execution failed. + SqliteQuery(request *SessionFsSqliteQueryRequest) (*SessionFsSqliteQueryResult, error) // Stat gets metadata for a path in the client-provided session filesystem. // // RPC method: sessionFs.stat. @@ -5170,6 +5241,44 @@ func RegisterClientSessionApiHandlers(client *jsonrpc2.Client, getHandlers func( } return raw, nil }) + client.SetRequestHandler("sessionFs.sqliteExists", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + var request SessionFsSqliteExistsRequest + if err := json.Unmarshal(params, &request); err != nil { + return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} + } + handlers := getHandlers(request.SessionID) + if handlers == nil || handlers.SessionFs == nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} + } + result, err := handlers.SessionFs.SqliteExists(&request) + if err != nil { + return nil, clientSessionHandlerError(err) + } + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil + }) + client.SetRequestHandler("sessionFs.sqliteQuery", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { + var request SessionFsSqliteQueryRequest + if err := json.Unmarshal(params, &request); err != nil { + return nil, &jsonrpc2.Error{Code: -32602, Message: fmt.Sprintf("Invalid params: %v", err)} + } + handlers := getHandlers(request.SessionID) + if handlers == nil || handlers.SessionFs == nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("No sessionFs handler registered for session: %s", request.SessionID)} + } + result, err := handlers.SessionFs.SqliteQuery(&request) + if err != nil { + return nil, clientSessionHandlerError(err) + } + raw, err := json.Marshal(result) + if err != nil { + return nil, &jsonrpc2.Error{Code: -32603, Message: fmt.Sprintf("Failed to marshal response: %v", err)} + } + return raw, nil + }) client.SetRequestHandler("sessionFs.stat", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) { var request SessionFsStatRequest if err := json.Unmarshal(params, &request); err != nil { diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go index ed6610c74..554b13893 100644 --- a/go/rpc/zrpc_encoding.go +++ b/go/rpc/zrpc_encoding.go @@ -289,17 +289,19 @@ func (r ExternalToolTextResultForLlmContentText) MarshalJSON() ([]byte, error) { func (r *ExternalToolTextResultForLlm) UnmarshalJSON(data []byte) error { type rawExternalToolTextResultForLlm struct { - Contents []json.RawMessage `json:"contents,omitempty"` - Error *string `json:"error,omitempty"` - ResultType *string `json:"resultType,omitempty"` - SessionLog *string `json:"sessionLog,omitempty"` - TextResultForLlm string `json:"textResultForLlm"` - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` + BinaryResultsForLlm []ExternalToolTextResultForLlmBinaryResultsForLlm `json:"binaryResultsForLlm,omitempty"` + Contents []json.RawMessage `json:"contents,omitempty"` + Error *string `json:"error,omitempty"` + ResultType *string `json:"resultType,omitempty"` + SessionLog *string `json:"sessionLog,omitempty"` + TextResultForLlm string `json:"textResultForLlm"` + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` } var raw rawExternalToolTextResultForLlm if err := json.Unmarshal(data, &raw); err != nil { return err } + r.BinaryResultsForLlm = raw.BinaryResultsForLlm if raw.Contents != nil { r.Contents = make([]ExternalToolTextResultForLlmContent, 0, len(raw.Contents)) for _, rawItem := range raw.Contents { @@ -348,7 +350,7 @@ func unmarshalFilterMapping(data []byte) (FilterMapping, error) { } } { - var value FilterMappingString + var value ContentFilterMode if err := json.Unmarshal(data, &value); err == nil { return value, nil } @@ -380,7 +382,6 @@ func (r *HandlePendingToolCallRequest) UnmarshalJSON(data []byte) error { func matchesMcpServerConfigHTTP(data []byte) bool { var rawGroup0 struct { - Args json.RawMessage `json:"args"` Command json.RawMessage `json:"command"` URL json.RawMessage `json:"url"` } @@ -390,24 +391,17 @@ func matchesMcpServerConfigHTTP(data []byte) bool { if rawGroup0.URL == nil { return false } - if rawGroup0.Args != nil { - return false - } return rawGroup0.Command == nil } -func matchesMcpServerConfigLocal(data []byte) bool { +func matchesMcpServerConfigStdio(data []byte) bool { var rawGroup0 struct { - Args json.RawMessage `json:"args"` Command json.RawMessage `json:"command"` URL json.RawMessage `json:"url"` } if err := json.Unmarshal(data, &rawGroup0); err != nil { return false } - if rawGroup0.Args == nil { - return false - } if rawGroup0.Command == nil { return false } @@ -425,8 +419,8 @@ func unmarshalMcpServerConfig(data []byte) (McpServerConfig, error) { } return &d, nil } - if matchesMcpServerConfigLocal(data) { - var d McpServerConfigLocal + if matchesMcpServerConfigStdio(data) { + var d McpServerConfigStdio if err := json.Unmarshal(data, &d); err != nil { return nil, err } @@ -444,6 +438,7 @@ func (r RawMcpServerConfigData) MarshalJSON() ([]byte, error) { func (r *McpServerConfigHTTP) UnmarshalJSON(data []byte) error { type rawMcpServerConfigHTTP struct { + Auth *McpServerConfigHTTPAuth `json:"auth,omitempty"` FilterMapping json.RawMessage `json:"filterMapping,omitempty"` Headers map[string]string `json:"headers,omitempty"` IsDefaultServer *bool `json:"isDefaultServer,omitempty"` @@ -459,6 +454,7 @@ func (r *McpServerConfigHTTP) UnmarshalJSON(data []byte) error { if err := json.Unmarshal(data, &raw); err != nil { return err } + r.Auth = raw.Auth if raw.FilterMapping != nil { value, err := unmarshalFilterMapping(raw.FilterMapping) if err != nil { @@ -478,19 +474,18 @@ func (r *McpServerConfigHTTP) UnmarshalJSON(data []byte) error { return nil } -func (r *McpServerConfigLocal) UnmarshalJSON(data []byte) error { - type rawMcpServerConfigLocal struct { - Args []string `json:"args"` - Command string `json:"command"` - Cwd *string `json:"cwd,omitempty"` - Env map[string]string `json:"env,omitempty"` - FilterMapping json.RawMessage `json:"filterMapping,omitempty"` - IsDefaultServer *bool `json:"isDefaultServer,omitempty"` - Timeout *int64 `json:"timeout,omitempty"` - Tools []string `json:"tools,omitempty"` - Type *McpServerConfigLocalType `json:"type,omitempty"` +func (r *McpServerConfigStdio) UnmarshalJSON(data []byte) error { + type rawMcpServerConfigStdio struct { + Args []string `json:"args,omitempty"` + Command string `json:"command"` + Cwd *string `json:"cwd,omitempty"` + Env map[string]string `json:"env,omitempty"` + FilterMapping json.RawMessage `json:"filterMapping,omitempty"` + IsDefaultServer *bool `json:"isDefaultServer,omitempty"` + Timeout *int64 `json:"timeout,omitempty"` + Tools []string `json:"tools,omitempty"` } - var raw rawMcpServerConfigLocal + var raw rawMcpServerConfigStdio if err := json.Unmarshal(data, &raw); err != nil { return err } @@ -508,7 +503,6 @@ func (r *McpServerConfigLocal) UnmarshalJSON(data []byte) error { r.IsDefaultServer = raw.IsDefaultServer r.Timeout = raw.Timeout r.Tools = raw.Tools - r.Type = raw.Type return nil } diff --git a/go/rpc/zsession_events.go b/go/rpc/zsession_events.go index 265c2a772..15e7088ea 100644 --- a/go/rpc/zsession_events.go +++ b/go/rpc/zsession_events.go @@ -147,10 +147,10 @@ func (*AssistantIntentData) Type() SessionEventType { return SessionEventTypeAss // Agent mode change details including previous and new modes type SessionModeChangedData struct { - // Agent mode after the change (e.g., "interactive", "plan", "autopilot") - NewMode string `json:"newMode"` - // Agent mode before the change (e.g., "interactive", "plan", "autopilot") - PreviousMode string `json:"previousMode"` + // The session mode the agent is operating in + NewMode SessionMode `json:"newMode"` + // The session mode the agent is operating in + PreviousMode SessionMode `json:"previousMode"` } func (*SessionModeChangedData) sessionEventData() {} @@ -209,8 +209,8 @@ func (*AssistantMessageData) Type() SessionEventType { return SessionEventTypeAs type AutoModeSwitchCompletedData struct { // Request ID of the resolved request; clients should dismiss any UI for this request RequestID string `json:"requestId"` - // The user's choice: 'yes', 'yes_always', or 'no' - Response string `json:"response"` + // The user's auto-mode-switch choice + Response AutoModeSwitchResponse `json:"response"` } func (*AutoModeSwitchCompletedData) sessionEventData() {} @@ -552,7 +552,7 @@ type AssistantUsageData struct { ProviderCallID *string `json:"providerCallId,omitempty"` // Per-quota resource usage snapshots, keyed by quota identifier QuotaSnapshots map[string]AssistantUsageQuotaSnapshot `json:"quotaSnapshots,omitempty"` - // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Number of output tokens used for reasoning (e.g., chain-of-thought) ReasoningTokens *float64 `json:"reasoningTokens,omitempty"` @@ -677,12 +677,12 @@ func (*PermissionRequestedData) Type() SessionEventType { return SessionEventTyp // Plan approval request with plan content and available user actions type ExitPlanModeRequestedData struct { - // Available actions the user can take (e.g., approve, edit, reject) - Actions []string `json:"actions"` + // Available actions the user can take + Actions []ExitPlanModeAction `json:"actions"` // Full content of the plan file PlanContent string `json:"planContent"` - // The recommended action for the user to take - RecommendedAction string `json:"recommendedAction"` + // Recommended action to preselect for the user + RecommendedAction ExitPlanModeAction `json:"recommendedAction"` // Unique identifier for this request; used to respond via session.respondToExitPlanMode() RequestID string `json:"requestId"` // Summary of the plan that was created @@ -713,8 +713,8 @@ type ExitPlanModeCompletedData struct { Feedback *string `json:"feedback,omitempty"` // Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request RequestID string `json:"requestId"` - // Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') - SelectedAction *string `json:"selectedAction,omitempty"` + // Action selected by the user + SelectedAction *ExitPlanModeAction `json:"selectedAction,omitempty"` } func (*ExitPlanModeCompletedData) sessionEventData() {} @@ -857,8 +857,8 @@ func (*SessionExtensionsLoadedData) Type() SessionEventType { type SessionMcpServerStatusChangedData struct { // Name of the MCP server whose status changed ServerName string `json:"serverName"` - // New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status McpServerStatusChangedStatus `json:"status"` + // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + Status McpServerStatus `json:"status"` } func (*SessionMcpServerStatusChangedData) sessionEventData() {} @@ -964,7 +964,7 @@ type SessionStartData struct { DetachedFromSpawningParentSessionID *string `json:"detachedFromSpawningParentSessionId,omitempty"` // Identifier of the software producing the events (e.g., "copilot-agent") Producer string `json:"producer"` - // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` @@ -993,7 +993,7 @@ type SessionResumeData struct { ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` // Total number of persisted events in the session at the time of resume EventCount float64 `json:"eventCount"` - // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + // Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") ReasoningEffort *string `json:"reasoningEffort,omitempty"` // Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") ReasoningSummary *ReasoningSummary `json:"reasoningSummary,omitempty"` @@ -1659,9 +1659,9 @@ type McpServersLoadedServer struct { // Server name (config key) Name string `json:"name"` // Configuration source: user, workspace, plugin, or builtin - Source *string `json:"source,omitempty"` + Source *McpServerSource `json:"source,omitempty"` // Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - Status McpServersLoadedServerStatus `json:"status"` + Status McpServerStatus `json:"status"` } // Derived user-facing permission prompt details for UI consumers @@ -1787,11 +1787,11 @@ func (PermissionPromptRequestMcp) Kind() PermissionPromptRequestKind { // Memory operation permission prompt type PermissionPromptRequestMemory struct { // Whether this is a store or vote memory operation - Action *PermissionPromptRequestMemoryAction `json:"action,omitempty"` + Action *PermissionRequestMemoryAction `json:"action,omitempty"` // Source references for the stored fact (store only) Citations *string `json:"citations,omitempty"` // Vote direction (vote only) - Direction *PermissionPromptRequestMemoryDirection `json:"direction,omitempty"` + Direction *PermissionRequestMemoryDirection `json:"direction,omitempty"` // The fact being stored or voted on Fact string `json:"fact"` // Reason for the vote (vote only) @@ -2282,8 +2282,8 @@ type SkillsLoadedSkill struct { Name string `json:"name"` // Absolute path to the skill file, if available Path *string `json:"path,omitempty"` - // Source location type of the skill (e.g., project, personal, plugin) - Source string `json:"source"` + // Source location type (e.g., project, personal-copilot, plugin, builtin) + Source SkillSource `json:"source"` // Whether the skill can be invoked by the user as a slash command UserInvocable bool `json:"userInvocable"` } @@ -2820,6 +2820,15 @@ const ( AssistantUsageAPIEndpointWsResponses AssistantUsageAPIEndpoint = "ws:/responses" ) +// The user's auto-mode-switch choice +type AutoModeSwitchResponse string + +const ( + AutoModeSwitchResponseNo AutoModeSwitchResponse = "no" + AutoModeSwitchResponseYes AutoModeSwitchResponse = "yes" + AutoModeSwitchResponseYesAlways AutoModeSwitchResponse = "yes_always" +) + // The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed) type ElicitationCompletedAction string @@ -2844,6 +2853,16 @@ const ( ElicitationRequestedSchemaTypeObject ElicitationRequestedSchemaType = "object" ) +// Exit plan mode action +type ExitPlanModeAction string + +const ( + ExitPlanModeActionAutopilot ExitPlanModeAction = "autopilot" + ExitPlanModeActionAutopilotFleet ExitPlanModeAction = "autopilot_fleet" + ExitPlanModeActionExitOnly ExitPlanModeAction = "exit_only" + ExitPlanModeActionInteractive ExitPlanModeAction = "interactive" +) + // Discovery source type ExtensionsLoadedExtensionSource string @@ -2877,30 +2896,6 @@ const ( McpOauthRequiredStaticClientConfigGrantTypeClientCredentials McpOauthRequiredStaticClientConfigGrantType = "client_credentials" ) -// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type McpServersLoadedServerStatus string - -const ( - McpServersLoadedServerStatusConnected McpServersLoadedServerStatus = "connected" - McpServersLoadedServerStatusDisabled McpServersLoadedServerStatus = "disabled" - McpServersLoadedServerStatusFailed McpServersLoadedServerStatus = "failed" - McpServersLoadedServerStatusNeedsAuth McpServersLoadedServerStatus = "needs-auth" - McpServersLoadedServerStatusNotConfigured McpServersLoadedServerStatus = "not_configured" - McpServersLoadedServerStatusPending McpServersLoadedServerStatus = "pending" -) - -// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured -type McpServerStatusChangedStatus string - -const ( - McpServerStatusChangedStatusConnected McpServerStatusChangedStatus = "connected" - McpServerStatusChangedStatusDisabled McpServerStatusChangedStatus = "disabled" - McpServerStatusChangedStatusFailed McpServerStatusChangedStatus = "failed" - McpServerStatusChangedStatusNeedsAuth McpServerStatusChangedStatus = "needs-auth" - McpServerStatusChangedStatusNotConfigured McpServerStatusChangedStatus = "not_configured" - McpServerStatusChangedStatusPending McpServerStatusChangedStatus = "pending" -) - // Where the failed model call originated type ModelCallFailureSource string @@ -2927,22 +2922,6 @@ const ( PermissionPromptRequestKindWrite PermissionPromptRequestKind = "write" ) -// Whether this is a store or vote memory operation -type PermissionPromptRequestMemoryAction string - -const ( - PermissionPromptRequestMemoryActionStore PermissionPromptRequestMemoryAction = "store" - PermissionPromptRequestMemoryActionVote PermissionPromptRequestMemoryAction = "vote" -) - -// Vote direction (vote only) -type PermissionPromptRequestMemoryDirection string - -const ( - PermissionPromptRequestMemoryDirectionDownvote PermissionPromptRequestMemoryDirection = "downvote" - PermissionPromptRequestMemoryDirectionUpvote PermissionPromptRequestMemoryDirection = "upvote" -) - // Underlying permission kind that needs path approval type PermissionPromptRequestPathAccessKind string diff --git a/go/session_fs_provider.go b/go/session_fs_provider.go index 197b3fc20..6051a5e4a 100644 --- a/go/session_fs_provider.go +++ b/go/session_fs_provider.go @@ -44,6 +44,10 @@ type SessionFsProvider interface { Rm(path string, recursive bool, force bool) error // Rename moves/renames a file or directory. Rename(src string, dest string) error + // SqliteQuery executes a SQLite query against the provider's per-session database. + SqliteQuery(sessionID string, query string, queryType rpc.SessionFsSqliteQueryType, params map[string]any) (*rpc.SessionFsSqliteQueryResult, error) + // SqliteExists checks whether the provider has a SQLite database for the session. + SqliteExists(sessionID string) (bool, error) } // SessionFsFileInfo holds file metadata returned by SessionFsProvider.Stat. @@ -164,6 +168,27 @@ func (a *sessionFsAdapter) Rename(request *rpc.SessionFsRenameRequest) (*rpc.Ses return nil, nil } +func (a *sessionFsAdapter) SqliteQuery(request *rpc.SessionFsSqliteQueryRequest) (*rpc.SessionFsSqliteQueryResult, error) { + result, err := a.provider.SqliteQuery(request.SessionID, request.Query, request.QueryType, request.Params) + if err != nil { + return &rpc.SessionFsSqliteQueryResult{ + Columns: []string{}, + Rows: []map[string]any{}, + RowsAffected: 0, + Error: toSessionFsError(err), + }, nil + } + return result, nil +} + +func (a *sessionFsAdapter) SqliteExists(request *rpc.SessionFsSqliteExistsRequest) (*rpc.SessionFsSqliteExistsResult, error) { + exists, err := a.provider.SqliteExists(request.SessionID) + if err != nil { + return &rpc.SessionFsSqliteExistsResult{Exists: false}, nil + } + return &rpc.SessionFsSqliteExistsResult{Exists: exists}, nil +} + func toSessionFsError(err error) *rpc.SessionFsError { code := rpc.SessionFsErrorCodeUNKNOWN if errors.Is(err, os.ErrNotExist) { diff --git a/go/types.go b/go/types.go index 1bea26d84..cc30ff9d7 100644 --- a/go/types.go +++ b/go/types.go @@ -327,18 +327,6 @@ type AutoModeSwitchRequest struct { RetryAfterSeconds *float64 `json:"retryAfterSeconds,omitempty"` } -// AutoModeSwitchResponse is the user's response to an auto-mode-switch request. -type AutoModeSwitchResponse string - -const ( - // AutoModeSwitchResponseYes approves the switch for this rate-limit cycle. - AutoModeSwitchResponseYes AutoModeSwitchResponse = "yes" - // AutoModeSwitchResponseYesAlways approves and remembers the choice for this session. - AutoModeSwitchResponseYesAlways AutoModeSwitchResponse = "yes_always" - // AutoModeSwitchResponseNo declines the switch. - AutoModeSwitchResponseNo AutoModeSwitchResponse = "no" -) - // AutoModeSwitchInvocation provides context about an auto-mode-switch request. type AutoModeSwitchInvocation struct { SessionID string diff --git a/go/zsession_events.go b/go/zsession_events.go index 170b58c93..b5ed49a97 100644 --- a/go/zsession_events.go +++ b/go/zsession_events.go @@ -29,6 +29,7 @@ type ( AttachmentType = rpc.AttachmentType AutoModeSwitchCompletedData = rpc.AutoModeSwitchCompletedData AutoModeSwitchRequestedData = rpc.AutoModeSwitchRequestedData + AutoModeSwitchResponse = rpc.AutoModeSwitchResponse CapabilitiesChangedData = rpc.CapabilitiesChangedData CapabilitiesChangedUI = rpc.CapabilitiesChangedUI CommandCompletedData = rpc.CommandCompletedData @@ -54,6 +55,7 @@ type ( ElicitationRequestedSchemaType = rpc.ElicitationRequestedSchemaType EmbeddedBlobResourceContents = rpc.EmbeddedBlobResourceContents EmbeddedTextResourceContents = rpc.EmbeddedTextResourceContents + ExitPlanModeAction = rpc.ExitPlanModeAction ExitPlanModeCompletedData = rpc.ExitPlanModeCompletedData ExitPlanModeRequestedData = rpc.ExitPlanModeRequestedData ExtensionsLoadedExtension = rpc.ExtensionsLoadedExtension @@ -71,8 +73,8 @@ type ( McpOauthRequiredStaticClientConfig = rpc.McpOauthRequiredStaticClientConfig McpOauthRequiredStaticClientConfigGrantType = rpc.McpOauthRequiredStaticClientConfigGrantType McpServersLoadedServer = rpc.McpServersLoadedServer - McpServersLoadedServerStatus = rpc.McpServersLoadedServerStatus - McpServerStatusChangedStatus = rpc.McpServerStatusChangedStatus + McpServerSource = rpc.McpServerSource + McpServerStatus = rpc.McpServerStatus ModelCallFailureData = rpc.ModelCallFailureData ModelCallFailureSource = rpc.ModelCallFailureSource PendingMessagesModifiedData = rpc.PendingMessagesModifiedData @@ -95,8 +97,6 @@ type ( PermissionPromptRequestKind = rpc.PermissionPromptRequestKind PermissionPromptRequestMcp = rpc.PermissionPromptRequestMcp PermissionPromptRequestMemory = rpc.PermissionPromptRequestMemory - PermissionPromptRequestMemoryAction = rpc.PermissionPromptRequestMemoryAction - PermissionPromptRequestMemoryDirection = rpc.PermissionPromptRequestMemoryDirection PermissionPromptRequestPath = rpc.PermissionPromptRequestPath PermissionPromptRequestPathAccessKind = rpc.PermissionPromptRequestPathAccessKind PermissionPromptRequestRead = rpc.PermissionPromptRequestRead @@ -152,6 +152,7 @@ type ( SessionInfoData = rpc.SessionInfoData SessionMcpServersLoadedData = rpc.SessionMcpServersLoadedData SessionMcpServerStatusChangedData = rpc.SessionMcpServerStatusChangedData + SessionMode = rpc.SessionMode SessionModeChangedData = rpc.SessionModeChangedData SessionModelChangeData = rpc.SessionModelChangeData SessionPlanChangedData = rpc.SessionPlanChangedData @@ -179,6 +180,7 @@ type ( ShutdownType = rpc.ShutdownType SkillInvokedData = rpc.SkillInvokedData SkillsLoadedSkill = rpc.SkillsLoadedSkill + SkillSource = rpc.SkillSource SubagentCompletedData = rpc.SubagentCompletedData SubagentDeselectedData = rpc.SubagentDeselectedData SubagentFailedData = rpc.SubagentFailedData @@ -262,12 +264,19 @@ const ( AttachmentTypeFile = rpc.AttachmentTypeFile AttachmentTypeGithubReference = rpc.AttachmentTypeGithubReference AttachmentTypeSelection = rpc.AttachmentTypeSelection + AutoModeSwitchResponseNo = rpc.AutoModeSwitchResponseNo + AutoModeSwitchResponseYes = rpc.AutoModeSwitchResponseYes + AutoModeSwitchResponseYesAlways = rpc.AutoModeSwitchResponseYesAlways ElicitationCompletedActionAccept = rpc.ElicitationCompletedActionAccept ElicitationCompletedActionCancel = rpc.ElicitationCompletedActionCancel ElicitationCompletedActionDecline = rpc.ElicitationCompletedActionDecline ElicitationRequestedModeForm = rpc.ElicitationRequestedModeForm ElicitationRequestedModeURL = rpc.ElicitationRequestedModeURL ElicitationRequestedSchemaTypeObject = rpc.ElicitationRequestedSchemaTypeObject + ExitPlanModeActionAutopilot = rpc.ExitPlanModeActionAutopilot + ExitPlanModeActionAutopilotFleet = rpc.ExitPlanModeActionAutopilotFleet + ExitPlanModeActionExitOnly = rpc.ExitPlanModeActionExitOnly + ExitPlanModeActionInteractive = rpc.ExitPlanModeActionInteractive ExtensionsLoadedExtensionSourceProject = rpc.ExtensionsLoadedExtensionSourceProject ExtensionsLoadedExtensionSourceUser = rpc.ExtensionsLoadedExtensionSourceUser ExtensionsLoadedExtensionStatusDisabled = rpc.ExtensionsLoadedExtensionStatusDisabled @@ -277,18 +286,16 @@ const ( HandoffSourceTypeLocal = rpc.HandoffSourceTypeLocal HandoffSourceTypeRemote = rpc.HandoffSourceTypeRemote McpOauthRequiredStaticClientConfigGrantTypeClientCredentials = rpc.McpOauthRequiredStaticClientConfigGrantTypeClientCredentials - McpServersLoadedServerStatusConnected = rpc.McpServersLoadedServerStatusConnected - McpServersLoadedServerStatusDisabled = rpc.McpServersLoadedServerStatusDisabled - McpServersLoadedServerStatusFailed = rpc.McpServersLoadedServerStatusFailed - McpServersLoadedServerStatusNeedsAuth = rpc.McpServersLoadedServerStatusNeedsAuth - McpServersLoadedServerStatusNotConfigured = rpc.McpServersLoadedServerStatusNotConfigured - McpServersLoadedServerStatusPending = rpc.McpServersLoadedServerStatusPending - McpServerStatusChangedStatusConnected = rpc.McpServerStatusChangedStatusConnected - McpServerStatusChangedStatusDisabled = rpc.McpServerStatusChangedStatusDisabled - McpServerStatusChangedStatusFailed = rpc.McpServerStatusChangedStatusFailed - McpServerStatusChangedStatusNeedsAuth = rpc.McpServerStatusChangedStatusNeedsAuth - McpServerStatusChangedStatusNotConfigured = rpc.McpServerStatusChangedStatusNotConfigured - McpServerStatusChangedStatusPending = rpc.McpServerStatusChangedStatusPending + McpServerSourceBuiltin = rpc.McpServerSourceBuiltin + McpServerSourcePlugin = rpc.McpServerSourcePlugin + McpServerSourceUser = rpc.McpServerSourceUser + McpServerSourceWorkspace = rpc.McpServerSourceWorkspace + McpServerStatusConnected = rpc.McpServerStatusConnected + McpServerStatusDisabled = rpc.McpServerStatusDisabled + McpServerStatusFailed = rpc.McpServerStatusFailed + McpServerStatusNeedsAuth = rpc.McpServerStatusNeedsAuth + McpServerStatusNotConfigured = rpc.McpServerStatusNotConfigured + McpServerStatusPending = rpc.McpServerStatusPending ModelCallFailureSourceMcpSampling = rpc.ModelCallFailureSourceMcpSampling ModelCallFailureSourceSubagent = rpc.ModelCallFailureSourceSubagent ModelCallFailureSourceTopLevel = rpc.ModelCallFailureSourceTopLevel @@ -303,10 +310,6 @@ const ( PermissionPromptRequestKindRead = rpc.PermissionPromptRequestKindRead PermissionPromptRequestKindURL = rpc.PermissionPromptRequestKindURL PermissionPromptRequestKindWrite = rpc.PermissionPromptRequestKindWrite - PermissionPromptRequestMemoryActionStore = rpc.PermissionPromptRequestMemoryActionStore - PermissionPromptRequestMemoryActionVote = rpc.PermissionPromptRequestMemoryActionVote - PermissionPromptRequestMemoryDirectionDownvote = rpc.PermissionPromptRequestMemoryDirectionDownvote - PermissionPromptRequestMemoryDirectionUpvote = rpc.PermissionPromptRequestMemoryDirectionUpvote PermissionPromptRequestPathAccessKindRead = rpc.PermissionPromptRequestPathAccessKindRead PermissionPromptRequestPathAccessKindShell = rpc.PermissionPromptRequestPathAccessKindShell PermissionPromptRequestPathAccessKindWrite = rpc.PermissionPromptRequestPathAccessKindWrite @@ -420,8 +423,18 @@ const ( SessionEventTypeUserInputCompleted = rpc.SessionEventTypeUserInputCompleted SessionEventTypeUserInputRequested = rpc.SessionEventTypeUserInputRequested SessionEventTypeUserMessage = rpc.SessionEventTypeUserMessage + SessionModeAutopilot = rpc.SessionModeAutopilot + SessionModeInteractive = rpc.SessionModeInteractive + SessionModePlan = rpc.SessionModePlan ShutdownTypeError = rpc.ShutdownTypeError ShutdownTypeRoutine = rpc.ShutdownTypeRoutine + SkillSourceBuiltin = rpc.SkillSourceBuiltin + SkillSourceCustom = rpc.SkillSourceCustom + SkillSourceInherited = rpc.SkillSourceInherited + SkillSourcePersonalAgents = rpc.SkillSourcePersonalAgents + SkillSourcePersonalCopilot = rpc.SkillSourcePersonalCopilot + SkillSourcePlugin = rpc.SkillSourcePlugin + SkillSourceProject = rpc.SkillSourceProject SystemMessageRoleDeveloper = rpc.SystemMessageRoleDeveloper SystemMessageRoleSystem = rpc.SystemMessageRoleSystem SystemNotificationAgentCompletedStatusCompleted = rpc.SystemNotificationAgentCompletedStatusCompleted diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index b043d72d5..d2976852d 100644 --- a/nodejs/package-lock.json +++ b/nodejs/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.8", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.49-1", + "@github/copilot": "^1.0.49-6", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,28 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.49-1.tgz", - "integrity": "sha512-1euPT6WXtLWnoqz1SXHdcqmktucdkfwfZn/Eo4iQ1FAjZo7awuN86rVb1feDwxY4vlSGbzNmK+GDKDgs9qZCDg==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.49-6.tgz", + "integrity": "sha512-9ptx1Vs6aJvybo7vN1gGHNPHt5JqmhIZWyurnMMFjoZh6DAq9NO+0yWBP1WL752ycFDE/kKR+OgKC64O+UsLQw==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.49-1", - "@github/copilot-darwin-x64": "1.0.49-1", - "@github/copilot-linux-arm64": "1.0.49-1", - "@github/copilot-linux-x64": "1.0.49-1", - "@github/copilot-win32-arm64": "1.0.49-1", - "@github/copilot-win32-x64": "1.0.49-1" + "@github/copilot-darwin-arm64": "1.0.49-6", + "@github/copilot-darwin-x64": "1.0.49-6", + "@github/copilot-linux-arm64": "1.0.49-6", + "@github/copilot-linux-x64": "1.0.49-6", + "@github/copilot-linuxmusl-arm64": "1.0.49-6", + "@github/copilot-linuxmusl-x64": "1.0.49-6", + "@github/copilot-win32-arm64": "1.0.49-6", + "@github/copilot-win32-x64": "1.0.49-6" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.49-1.tgz", - "integrity": "sha512-EgHdwlkYSJ+RmHAelGGpQxQe5/dgq3BlvToc0VmYEUCWO93ESEql7XBqCWYeASg3USUp8n87kf3mr2eXIECvLA==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.49-6.tgz", + "integrity": "sha512-e+0T9DIfaE5GyFnsIUQWSGhi/Ont9/iENLb43jyAsASxY+gWxqWyUHVD7kYJpunMODNjg0FNXgCEAX5YUyKOXQ==", "cpu": [ "arm64" ], @@ -696,9 +698,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.49-1.tgz", - "integrity": "sha512-YPtOW5q3vWB9Covn08jxqIdIjcCuJi/MgIlYk1ulKTINi5uK5a6NlsX2mDaGWL/svhDwDlhFEa3oUV41yOjTkg==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.49-6.tgz", + "integrity": "sha512-zgcMxEszygPvmki/A6aydLTMYyQhHQrQ5z+7BA/yGbuyghRKfe0mYG56QKRp5PsJJ01YK2Kr+G7EqgHypS5PVA==", "cpu": [ "x64" ], @@ -712,9 +714,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.49-1.tgz", - "integrity": "sha512-eEh0ec1UlWg8IdV2/3Zaxr/PAA86GclEFUcGNkwc9JceOgw5nhIdytsjCwXJUcRTzHsGrAoTS+Vad1RSvKSmYQ==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.49-6.tgz", + "integrity": "sha512-0/KlHumd38nYP/fNGVHaxSdqdRKV8cESaytTPyGq0ncfPMZzcqZhkgv6qur1XMai0zh0ZGpwxqzB3kwMrNJCoQ==", "cpu": [ "arm64" ], @@ -728,9 +730,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.49-1.tgz", - "integrity": "sha512-9+HxOVAbgCqcoyfAXyfaFxgIbAfHWCh699WuOfWViX2fjoKO3V0ZVHEergR4gVEgvnjvnmD0TZhT7+kTzqPK6A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.49-6.tgz", + "integrity": "sha512-LWfl79uh0i2/OTKikliZ3b0pnheVkEA2CYJTZUapfTSXKHQlQQIMR7HBhD6GCaOkVvHYgaH5WQVviedmS88N7g==", "cpu": [ "x64" ], @@ -743,10 +745,42 @@ "copilot-linux-x64": "copilot" } }, + "node_modules/@github/copilot-linuxmusl-arm64": { + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.49-6.tgz", + "integrity": "sha512-O9DdXVMdNdrgucLr4gd4djbzTdH8MGitNOWqIxTbsPy9YMd/OQ9JTEDCqPuezbiVzI0emS9NyrdSBasvcVI1VQ==", + "cpu": [ + "arm64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linuxmusl-arm64": "copilot" + } + }, + "node_modules/@github/copilot-linuxmusl-x64": { + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.49-6.tgz", + "integrity": "sha512-Yj8FTs9JcHvXw/FS8PEK0IxWa/qf+5UWPejburofi7hwiaC4wb+pX0AzhWee4jKcJ1YbZBEyWFMvBEK6xZ0TmA==", + "cpu": [ + "x64" + ], + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linuxmusl-x64": "copilot" + } + }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.49-1.tgz", - "integrity": "sha512-nsOz2rdk1Il3KJ24x3Hdv27MvotrKygIC/ok6acvq+xFwsYxR5Kt5bL1veBAGZVEG8K+0r2DfHi9NZHazBYK8A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.49-6.tgz", + "integrity": "sha512-u7GOWUIWRsS5IUdprXN2nWsTSTdM//m/LfqmOp1dfAxdSBOL4dF1Up3tEi8+f+HaCqIQhYQMryRux9KP4bUEnw==", "cpu": [ "arm64" ], @@ -760,9 +794,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.49-1.tgz", - "integrity": "sha512-RZbU3GESkfwd8UC1h5AeceVfCOfXjMA+sDKfIUyk8Pl8EukTNtNSf+WEKK1HzSxbxdbIu9DJyBL375JMwDiH4A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.49-6.tgz", + "integrity": "sha512-ayxTr2+miHaguaF0QrV6a/QvoMY4wUaTaYkZ7657ONOnR6BcFV9SNtnrlpLrbiIRkP6T0QZQXhROLaQwF7vrdw==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index ef06e3631..da8e93f3a 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.49-1", + "@github/copilot": "^1.0.49-6", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 4d317f0d9..db82994f2 100644 --- a/nodejs/samples/package-lock.json +++ b/nodejs/samples/package-lock.json @@ -18,7 +18,7 @@ "version": "0.1.8", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.49-1", + "@github/copilot": "^1.0.49-6", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index bc9c5486e..8182d9ad0 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -5,7 +5,7 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; -import type { EmbeddedBlobResourceContents, EmbeddedTextResourceContents, ReasoningSummary } from "./session-events.js"; +import type { EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, ReasoningSummary, SessionMode, SkillSource } from "./session-events.js"; /** * Authentication type @@ -44,19 +44,19 @@ export type QueuedCommandResult = QueuedCommandHandled | QueuedCommandNotHandled /** @experimental */ export type ConnectedRemoteSessionMetadataKind = "remote-session" | "coding-agent"; /** - * Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + * Controls how MCP tool result content is filtered: none leaves content unchanged, markdown sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes characters that can hide directives. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "DiscoveredMcpServerType". + * via the `definition` "ContentFilterMode". */ -export type DiscoveredMcpServerType = "stdio" | "http" | "sse" | "memory"; +export type ContentFilterMode = "none" | "markdown" | "hidden_characters"; /** - * Configuration source + * Server transport type: stdio, http, sse, or memory * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "DiscoveredMcpServerSource". + * via the `definition` "DiscoveredMcpServerType". */ -export type DiscoveredMcpServerSource = "user" | "workspace" | "plugin" | "builtin"; +export type DiscoveredMcpServerType = "stdio" | "http" | "sse" | "memory"; /** * Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/) * @@ -80,6 +80,13 @@ export type ExtensionStatus = "running" | "disabled" | "failed" | "starting"; * via the `definition` "ExternalToolResult". */ export type ExternalToolResult = string | ExternalToolTextResultForLlm; +/** + * Binary result type discriminator. Use "image" for images and "resource" for other binary data. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmBinaryResultsForLlmType". + */ +export type ExternalToolTextResultForLlmBinaryResultsForLlmType = "image" | "resource"; /** * A content block within a tool result, which may be text, terminal output, image, audio, or a resource * @@ -117,23 +124,9 @@ export type ExternalToolTextResultForLlmContentResourceDetails = */ export type FilterMapping = | { - [k: string]: FilterMappingValue; + [k: string]: ContentFilterMode; } - | FilterMappingString; -/** - * Allowed values for the `FilterMappingValue` enumeration. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FilterMappingValue". - */ -export type FilterMappingValue = "none" | "markdown" | "hidden_characters"; -/** - * Allowed values for the `FilterMappingString` enumeration. - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "FilterMappingString". - */ -export type FilterMappingString = "none" | "markdown" | "hidden_characters"; + | ContentFilterMode; /** * Category of instruction source — used for merge logic * @@ -156,19 +149,12 @@ export type InstructionsSourcesLocation = "user" | "repository" | "working-direc */ export type SessionLogLevel = "info" | "warning" | "error"; /** - * MCP server configuration (local/stdio or remote/http) + * MCP server configuration (stdio process or remote HTTP/SSE) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema * via the `definition` "McpServerConfig". */ -export type McpServerConfig = McpServerConfigLocal | McpServerConfigHttp; -/** - * Local transport type. Defaults to "local". - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerConfigLocalType". - */ -export type McpServerConfigLocalType = "local" | "stdio"; +export type McpServerConfig = McpServerConfigStdio | McpServerConfigHttp; /** * Remote transport type. Defaults to "http" when omitted. * @@ -184,21 +170,12 @@ export type McpServerConfigHttpType = "http" | "sse"; */ export type McpServerConfigHttpOauthGrantType = "authorization_code" | "client_credentials"; /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * Current policy state for this model * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerStatus". + * via the `definition` "ModelPolicyState". */ -/** @experimental */ -export type McpServerStatus = "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; -/** - * Configuration source: user, workspace, plugin, or builtin - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerSource". - */ -/** @experimental */ -export type McpServerSource = "user" | "workspace" | "plugin" | "builtin"; +export type ModelPolicyState = "enabled" | "disabled" | "unconfigured"; /** * Model capability category for grouping in the model picker * @@ -213,13 +190,6 @@ export type ModelPickerCategory = "lightweight" | "versatile" | "powerful"; * via the `definition` "ModelPickerPriceCategory". */ export type ModelPickerPriceCategory = "low" | "medium" | "high" | "very_high"; -/** - * The agent mode. Valid values: "interactive", "plan", "autopilot". - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SessionMode". - */ -export type SessionMode = "interactive" | "plan" | "autopilot"; /** * Decision to apply to a pending permission request. * @@ -295,19 +265,19 @@ export type SessionFsReaddirWithTypesEntryType = "file" | "directory"; */ export type SessionFsSetProviderConventions = "windows" | "posix"; /** - * Signal to send (default: SIGTERM) + * How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ShellKillSignal". + * via the `definition` "SessionFsSqliteQueryType". */ -export type ShellKillSignal = "SIGTERM" | "SIGKILL" | "SIGINT"; +export type SessionFsSqliteQueryType = "exec" | "query" | "run"; /** - * Optional target session mode + * Signal to send (default: SIGTERM) * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "SlashCommandAgentPromptMode". + * via the `definition` "ShellKillSignal". */ -export type SlashCommandAgentPromptMode = "interactive" | "plan" | "autopilot"; +export type ShellKillSignal = "SIGTERM" | "SIGKILL" | "SIGINT"; /** * Result of invoking the slash command (text output, prompt to send to the agent, or completion). * @@ -322,18 +292,18 @@ export type SlashCommandInvocationResult = * Current lifecycle status of the task * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "TaskAgentInfoStatus". + * via the `definition` "TaskStatus". */ /** @experimental */ -export type TaskAgentInfoStatus = "running" | "idle" | "completed" | "failed" | "cancelled"; +export type TaskStatus = "running" | "idle" | "completed" | "failed" | "cancelled"; /** - * How the agent is currently being managed by the runtime + * Whether task execution is synchronously awaited or managed in the background * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "TaskAgentInfoExecutionMode". + * via the `definition` "TaskExecutionMode". */ /** @experimental */ -export type TaskAgentInfoExecutionMode = "sync" | "background"; +export type TaskExecutionMode = "sync" | "background"; /** * Schema for the `TaskInfo` type. * @@ -342,14 +312,6 @@ export type TaskAgentInfoExecutionMode = "sync" | "background"; */ /** @experimental */ export type TaskInfo = TaskAgentInfo | TaskShellInfo; -/** - * Current lifecycle status of the task - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "TaskShellInfoStatus". - */ -/** @experimental */ -export type TaskShellInfoStatus = "running" | "idle" | "completed" | "failed" | "cancelled"; /** * Whether the shell runs inside a managed PTY session or as an independent background process * @@ -358,14 +320,6 @@ export type TaskShellInfoStatus = "running" | "idle" | "completed" | "failed" | */ /** @experimental */ export type TaskShellInfoAttachmentMode = "attached" | "detached"; -/** - * Whether the shell command is currently sync-waited or background-managed - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "TaskShellInfoExecutionMode". - */ -/** @experimental */ -export type TaskShellInfoExecutionMode = "sync" | "background"; /** * Schema for the `UIElicitationFieldValue` type. * @@ -441,7 +395,7 @@ export interface AccountQuotaSnapshot { */ isUnlimitedEntitlement: boolean; /** - * Number of requests included in the entitlement + * Number of requests included in the entitlement, or -1 for unlimited entitlements */ entitlementRequests: number; /** @@ -875,7 +829,7 @@ export interface DiscoveredMcpServer { */ name: string; type?: DiscoveredMcpServerType; - source: DiscoveredMcpServerSource; + source: McpServerSource; /** * Whether the server is enabled (not in the disabled list) */ @@ -972,12 +926,37 @@ export interface ExternalToolTextResultForLlm { toolTelemetry?: { [k: string]: unknown; }; + /** + * Base64-encoded binary results returned to the model + */ + binaryResultsForLlm?: ExternalToolTextResultForLlmBinaryResultsForLlm[]; /** * Structured content blocks from the tool */ contents?: ExternalToolTextResultForLlmContent[]; [k: string]: unknown; } +/** + * Binary result returned by a tool for the model + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmBinaryResultsForLlm". + */ +export interface ExternalToolTextResultForLlmBinaryResultsForLlm { + type: ExternalToolTextResultForLlmBinaryResultsForLlmType; + /** + * Base64-encoded binary data + */ + data: string; + /** + * MIME type of the binary data + */ + mimeType: string; + /** + * Human-readable description of the binary data + */ + description?: string; +} /** * Plain text content block * @@ -1361,17 +1340,16 @@ export interface McpConfigAddRequest { config: McpServerConfig; } /** - * Local MCP server configuration launched as a child process. + * Stdio MCP server configuration launched as a child process. * * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "McpServerConfigLocal". + * via the `definition` "McpServerConfigStdio". */ -export interface McpServerConfigLocal { +export interface McpServerConfigStdio { /** * Tools to include. Defaults to all tools if not specified. */ tools?: string[]; - type?: McpServerConfigLocalType; /** * Whether this server is a built-in fallback used when the user has not configured their own server. */ @@ -1382,19 +1360,19 @@ export interface McpServerConfigLocal { */ timeout?: number; /** - * Executable command used to start the local MCP server process. + * Executable command used to start the Stdio MCP server process. */ command: string; /** - * Command-line arguments passed to the local MCP server process. + * Command-line arguments passed to the Stdio MCP server process. */ - args: string[]; + args?: string[]; /** - * Working directory for the local MCP server process. + * Working directory for the Stdio MCP server process. */ cwd?: string; /** - * Environment variables to pass to the local MCP server process. + * Environment variables to pass to the Stdio MCP server process. */ env?: { [k: string]: string; @@ -1440,6 +1418,19 @@ export interface McpServerConfigHttp { */ oauthPublicClient?: boolean; oauthGrantType?: McpServerConfigHttpOauthGrantType; + auth?: McpServerConfigHttpAuth; +} +/** + * Additional authentication configuration for this server. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "McpServerConfigHttpAuth". + */ +export interface McpServerConfigHttpAuth { + /** + * Fixed port for the OAuth redirect callback server. + */ + redirectPort?: number; } /** * MCP server names to disable for new sessions. @@ -1727,10 +1718,7 @@ export interface ModelCapabilitiesLimitsVision { * via the `definition` "ModelPolicy". */ export interface ModelPolicy { - /** - * Current policy state for this model - */ - state: string; + state: ModelPolicyState; /** * Usage terms or conditions for this model */ @@ -2510,10 +2498,7 @@ export interface ServerSkill { * Description of what the skill does */ description: string; - /** - * Source location type (e.g., project, personal-copilot, plugin, builtin) - */ - source: string; + source: SkillSource; /** * Whether the skill can be invoked by the user as a slash command */ @@ -2805,6 +2790,18 @@ export interface SessionFsRmRequest { */ force?: boolean; } +/** + * Optional capabilities declared by the provider + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSetProviderCapabilities". + */ +export interface SessionFsSetProviderCapabilities { + /** + * Whether the provider supports SQLite query/exists operations + */ + sqlite?: boolean; +} /** * Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. * @@ -2821,6 +2818,7 @@ export interface SessionFsSetProviderRequest { */ sessionStatePath: string; conventions: SessionFsSetProviderConventions; + capabilities?: SessionFsSetProviderCapabilities; } /** * Indicates whether the calling client was registered as the session filesystem provider. @@ -2834,6 +2832,68 @@ export interface SessionFsSetProviderResult { */ success: boolean; } +/** + * Indicates whether the per-session SQLite database already exists. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteExistsResult". + */ +export interface SessionFsSqliteExistsResult { + /** + * Whether the session database already exists + */ + exists: boolean; +} +/** + * SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteQueryRequest". + */ +export interface SessionFsSqliteQueryRequest { + /** + * Target session identifier + */ + sessionId: string; + /** + * SQL query to execute + */ + query: string; + queryType: SessionFsSqliteQueryType; + /** + * Optional named bind parameters + */ + params?: { + [k: string]: string | number | null; + }; +} +/** + * Query results including rows, columns, and rows affected, or a filesystem error if execution failed. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteQueryResult". + */ +export interface SessionFsSqliteQueryResult { + /** + * For SELECT: array of row objects. For others: empty array. + */ + rows: { + [k: string]: unknown; + }[]; + /** + * Column names from the result set + */ + columns: string[]; + /** + * Number of rows affected (for INSERT/UPDATE/DELETE) + */ + rowsAffected: number; + /** + * Last inserted row ID (for INSERT) + */ + lastInsertRowid?: number; + error?: SessionFsError; +} /** * Path whose metadata should be returned from the client-provided session filesystem. * @@ -3014,10 +3074,7 @@ export interface Skill { * Description of what the skill does */ description: string; - /** - * Source location type (e.g., project, personal, plugin) - */ - source: string; + source: SkillSource; /** * Whether the skill can be invoked by the user as a slash command */ @@ -3134,7 +3191,7 @@ export interface SlashCommandAgentPromptResult { * Prompt text to display to the user */ displayPrompt: string; - mode?: SlashCommandAgentPromptMode; + mode?: SessionMode; /** * True when the invocation mutated user runtime settings; consumers caching settings should refresh */ @@ -3212,7 +3269,7 @@ export interface TaskAgentInfo { * Short description of the task */ description: string; - status: TaskAgentInfoStatus; + status: TaskStatus; /** * ISO 8601 timestamp when the task was started */ @@ -3249,7 +3306,7 @@ export interface TaskAgentInfo { * Model used for the task when specified */ model?: string; - executionMode?: TaskAgentInfoExecutionMode; + executionMode?: TaskExecutionMode; /** * Whether the task is currently in the original sync wait and can be moved to background mode. False once it is already backgrounded, idle, finished, or no longer has a promotable sync waiter. */ @@ -3283,7 +3340,7 @@ export interface TaskShellInfo { * Short description of the task */ description: string; - status: TaskShellInfoStatus; + status: TaskStatus; /** * ISO 8601 timestamp when the task was started */ @@ -3297,7 +3354,7 @@ export interface TaskShellInfo { */ command: string; attachmentMode: TaskShellInfoAttachmentMode; - executionMode?: TaskShellInfoExecutionMode; + executionMode?: TaskExecutionMode; /** * Whether this shell task can be promoted to background mode */ @@ -4136,6 +4193,18 @@ export interface WorkspacesReadFileResult { */ content: string; } +/** + * Identifies the target session. + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "SessionFsSqliteExistsRequest". + */ +export interface SessionFsSqliteExistsRequest { + /** + * Target session identifier + */ + sessionId: string; +} /** Create typed server-scoped RPC methods (no session required). */ export function createServerRpc(connection: MessageConnection) { @@ -4350,7 +4419,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin /** * Gets the current agent interaction mode. * - * @returns The agent mode. Valid values: "interactive", "plan", "autopilot". + * @returns The session mode the agent is operating in */ get: async (): Promise => connection.sendRequest("session.mode.get", { sessionId }), @@ -4912,6 +4981,22 @@ export interface SessionFsHandler { * @returns Describes a filesystem error. */ rename(params: SessionFsRenameRequest): Promise; + /** + * Executes a SQLite query against the per-session database. + * + * @param params SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. + * + * @returns Query results including rows, columns, and rows affected, or a filesystem error if execution failed. + */ + sqliteQuery(params: SessionFsSqliteQueryRequest): Promise; + /** + * Checks whether the per-session SQLite database already exists, without creating it. + * + * @param params Identifies the target session. + * + * @returns Indicates whether the per-session SQLite database already exists. + */ + sqliteExists(params: SessionFsSqliteExistsRequest): Promise; } /** All client session API handler groups. */ @@ -4979,4 +5064,14 @@ export function registerClientSessionApiHandlers( if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); return handler.rename(params); }); + connection.onRequest("sessionFs.sqliteQuery", async (params: SessionFsSqliteQueryRequest) => { + const handler = getHandlers(params.sessionId).sessionFs; + if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); + return handler.sqliteQuery(params); + }); + connection.onRequest("sessionFs.sqliteExists", async (params: SessionFsSqliteExistsRequest) => { + const handler = getHandlers(params.sessionId).sessionFs; + if (!handler) throw new Error(`No sessionFs handler registered for session: ${params.sessionId}`); + return handler.sqliteExists(params); + }); } diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index 5b5404975..64c9e10ba 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -96,6 +96,10 @@ export type WorkingDirectoryContextHostType = "github" | "ado"; * Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") */ export type ReasoningSummary = "none" | "concise" | "detailed"; +/** + * The session mode the agent is operating in + */ +export type SessionMode = "interactive" | "plan" | "autopilot"; /** * The type of operation performed on the plan file */ @@ -218,14 +222,6 @@ export type PermissionPromptRequest = | PermissionPromptRequestHook | PermissionPromptRequestExtensionManagement | PermissionPromptRequestExtensionPermissionAccess; -/** - * Whether this is a store or vote memory operation - */ -export type PermissionPromptRequestMemoryAction = "store" | "vote"; -/** - * Vote direction (vote only) - */ -export type PermissionPromptRequestMemoryDirection = "upvote" | "downvote"; /** * Underlying permission kind that needs path approval */ @@ -280,25 +276,32 @@ export type CustomNotificationPayload = [k: string]: unknown; }; /** - * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * The user's auto-mode-switch choice */ -export type McpServersLoadedServerStatus = - | "connected" - | "failed" - | "needs-auth" - | "pending" - | "disabled" - | "not_configured"; +export type AutoModeSwitchResponse = "yes" | "yes_always" | "no"; /** - * New connection status: connected, failed, needs-auth, pending, disabled, or not_configured + * Exit plan mode action */ -export type McpServerStatusChangedStatus = - | "connected" - | "failed" - | "needs-auth" - | "pending" - | "disabled" - | "not_configured"; +export type ExitPlanModeAction = "exit_only" | "interactive" | "autopilot" | "autopilot_fleet"; +/** + * Source location type (e.g., project, personal-copilot, plugin, builtin) + */ +export type SkillSource = + | "project" + | "inherited" + | "personal-copilot" + | "personal-agents" + | "plugin" + | "custom" + | "builtin"; +/** + * Configuration source: user, workspace, plugin, or builtin + */ +export type McpServerSource = "user" | "workspace" | "plugin" | "builtin"; +/** + * Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + */ +export type McpServerStatus = "connected" | "failed" | "needs-auth" | "pending" | "disabled" | "not_configured"; /** * Discovery source */ @@ -360,7 +363,7 @@ export interface StartData { */ producer: string; /** - * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ reasoningEffort?: string; reasoningSummary?: ReasoningSummary; @@ -467,7 +470,7 @@ export interface ResumeData { */ eventCount: number; /** - * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ reasoningEffort?: string; reasoningSummary?: ReasoningSummary; @@ -955,14 +958,8 @@ export interface ModeChangedEvent { * Agent mode change details including previous and new modes */ export interface ModeChangedData { - /** - * Agent mode after the change (e.g., "interactive", "plan", "autopilot") - */ - newMode: string; - /** - * Agent mode before the change (e.g., "interactive", "plan", "autopilot") - */ - previousMode: string; + newMode: SessionMode; + previousMode: SessionMode; } /** * Session event "session.plan_changed". Plan file operation details indicating what changed @@ -2559,7 +2556,7 @@ export interface AssistantUsageData { [k: string]: AssistantUsageQuotaSnapshot; }; /** - * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + * Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") */ reasoningEffort?: string; /** @@ -4379,12 +4376,12 @@ export interface PermissionPromptRequestUrl { * Memory operation permission prompt */ export interface PermissionPromptRequestMemory { - action?: PermissionPromptRequestMemoryAction; + action?: PermissionRequestMemoryAction; /** * Source references for the stored fact (store only) */ citations?: string; - direction?: PermissionPromptRequestMemoryDirection; + direction?: PermissionRequestMemoryDirection; /** * The fact being stored or voted on */ @@ -5585,10 +5582,7 @@ export interface AutoModeSwitchCompletedData { * Request ID of the resolved request; clients should dismiss any UI for this request */ requestId: string; - /** - * The user's choice: 'yes', 'yes_always', or 'no' - */ - response: string; + response: AutoModeSwitchResponse; } /** * Session event "commands.changed". SDK command registration change notification @@ -5722,17 +5716,14 @@ export interface ExitPlanModeRequestedEvent { */ export interface ExitPlanModeRequestedData { /** - * Available actions the user can take (e.g., approve, edit, reject) + * Available actions the user can take */ - actions: string[]; + actions: ExitPlanModeAction[]; /** * Full content of the plan file */ planContent: string; - /** - * The recommended action for the user to take - */ - recommendedAction: string; + recommendedAction: ExitPlanModeAction; /** * Unique identifier for this request; used to respond via session.respondToExitPlanMode() */ @@ -5792,10 +5783,7 @@ export interface ExitPlanModeCompletedData { * Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request */ requestId: string; - /** - * Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') - */ - selectedAction?: string; + selectedAction?: ExitPlanModeAction; } /** * Session event "session.tools_updated". @@ -5929,10 +5917,7 @@ export interface SkillsLoadedSkill { * Absolute path to the skill file, if available */ path?: string; - /** - * Source location type of the skill (e.g., project, personal, plugin) - */ - source: string; + source: SkillSource; /** * Whether the skill can be invoked by the user as a slash command */ @@ -6073,11 +6058,8 @@ export interface McpServersLoadedServer { * Server name (config key) */ name: string; - /** - * Configuration source: user, workspace, plugin, or builtin - */ - source?: string; - status: McpServersLoadedServerStatus; + source?: McpServerSource; + status: McpServerStatus; } /** * Session event "session.mcp_server_status_changed". @@ -6117,7 +6099,7 @@ export interface McpServerStatusChangedData { * Name of the MCP server whose status changed */ serverName: string; - status: McpServerStatusChangedStatus; + status: McpServerStatus; } /** * Session event "session.extensions_loaded". diff --git a/nodejs/src/sessionFsProvider.ts b/nodejs/src/sessionFsProvider.ts index 920ea3cd1..589a30358 100644 --- a/nodejs/src/sessionFsProvider.ts +++ b/nodejs/src/sessionFsProvider.ts @@ -7,6 +7,8 @@ import type { SessionFsError, SessionFsStatResult, SessionFsReaddirWithTypesEntry, + SessionFsSqliteQueryResult, + SessionFsSqliteQueryType, } from "./generated/rpc.js"; /** @@ -55,6 +57,17 @@ export interface SessionFsProvider { /** Renames/moves a file or directory. */ rename(src: string, dest: string): Promise; + + /** Executes a SQLite query against the provider's per-session database. */ + sqliteQuery( + sessionId: string, + query: string, + queryType: SessionFsSqliteQueryType, + params?: Record + ): Promise; + + /** Checks whether the provider has a SQLite database for the session. */ + sqliteExists(sessionId: string): Promise; } /** @@ -149,6 +162,25 @@ export function createSessionFsAdapter(provider: SessionFsProvider): SessionFsHa return toSessionFsError(err); } }, + sqliteQuery: async ({ sessionId, query, queryType, params }) => { + try { + return await provider.sqliteQuery(sessionId, query, queryType, params); + } catch (err) { + return { + columns: [], + rows: [], + rowsAffected: 0, + error: toSessionFsError(err), + }; + } + }, + sqliteExists: async ({ sessionId }) => { + try { + return { exists: await provider.sqliteExists(sessionId) }; + } catch { + return { exists: false }; + } + }, }; } diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 4d38eb5b9..f18e18ac1 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -233,7 +233,7 @@ export type ToolResultType = "success" | "failure" | "rejected" | "denied" | "ti export type ToolBinaryResult = { data: string; mimeType: string; - type: string; + type: "image" | "resource"; description?: string; }; diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index 7fe2b9cc7..af9642a50 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -14,6 +14,7 @@ import { CapiProxy } from "./CapiProxy"; import { retry, formatError } from "./sdkTestHelper"; export const isCI = process.env.GITHUB_ACTIONS === "true"; +export const DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests"; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -35,12 +36,24 @@ export async function createSdkTestContext({ const openAiEndpoint = new CapiProxy(); const proxyUrl = await openAiEndpoint.start(); + await openAiEndpoint.setCopilotUserByToken(DEFAULT_GITHUB_TOKEN, { + login: "e2e-test-user", + copilot_plan: "individual_pro", + endpoints: { + api: proxyUrl, + telemetry: "https://localhost:1/telemetry", + }, + analytics_tracking_id: "e2e-test-tracking-id", + }); const env = { ...process.env, ...openAiEndpoint.getProxyEnv(), COPILOT_API_URL: proxyUrl, COPILOT_HOME: copilotHomeDir, + COPILOT_SDK_AUTH_TOKEN: DEFAULT_GITHUB_TOKEN, GH_CONFIG_DIR: homeDir, + GH_TOKEN: DEFAULT_GITHUB_TOKEN, + GITHUB_TOKEN: DEFAULT_GITHUB_TOKEN, // TODO: I'm not convinced the SDK should default to using whatever config you happen to have in your homedir. // The SDK config should be independent of the regular CLI app. Likewise it shouldn't mix sessions from the @@ -48,18 +61,13 @@ export async function createSdkTestContext({ XDG_CONFIG_HOME: homeDir, XDG_STATE_HOME: homeDir, }; - if (isCI) { - env.GH_TOKEN = "fake-token-for-e2e-tests"; - env.GITHUB_TOKEN = "fake-token-for-e2e-tests"; - } const copilotClient = new CopilotClient({ cwd: workDir, env, logLevel: logLevel || "error", cliPath: process.env.COPILOT_CLI_PATH, - // Use fake token in CI to allow cached responses without real auth - gitHubToken: isCI ? "fake-token-for-e2e-tests" : undefined, + gitHubToken: DEFAULT_GITHUB_TOKEN, useStdio: useStdio, ...copilotClientOptions, }); diff --git a/nodejs/test/e2e/per_session_auth.e2e.test.ts b/nodejs/test/e2e/per_session_auth.e2e.test.ts index 8ba753069..e2bf6c197 100644 --- a/nodejs/test/e2e/per_session_auth.e2e.test.ts +++ b/nodejs/test/e2e/per_session_auth.e2e.test.ts @@ -3,11 +3,11 @@ *--------------------------------------------------------------------------------------------*/ import { describe, expect, it } from "vitest"; -import { approveAll } from "../../src/index.js"; +import { approveAll, CopilotClient } from "../../src/index.js"; import { createSdkTestContext } from "./harness/sdkTestContext.js"; describe("Per-session GitHub auth", async () => { - const { copilotClient: client, openAiEndpoint, env } = await createSdkTestContext(); + const { copilotClient: client, openAiEndpoint, env, workDir } = await createSdkTestContext(); // Redirect GitHub API calls (e.g., fetchCopilotUser) to the proxy // so per-session auth token resolution can be tested @@ -76,17 +76,32 @@ describe("Per-session GitHub auth", async () => { }); it("should return unauthenticated when no token is provided", async () => { - const session = await client.createSession({ - onPermissionRequest: approveAll, + const noTokenClient = new CopilotClient({ + cwd: workDir, + env: withoutAuthEnv({ + ...env, + COPILOT_DEBUG_GITHUB_API_URL: env.COPILOT_API_URL, + }), + logLevel: "error", + cliPath: process.env.COPILOT_CLI_PATH, + useLoggedInUser: false, }); - const authStatus = await session.rpc.auth.getStatus(); - // Without a per-session GitHub token, there is no per-session identity. - // In CI the process-level fake token may still authenticate globally, - // so we check login rather than isAuthenticated. - expect(authStatus.login).toBeFalsy(); - - await session.disconnect(); + try { + const session = await noTokenClient.createSession({ + onPermissionRequest: approveAll, + }); + + const authStatus = await session.rpc.auth.getStatus(); + // Without a per-session GitHub token, there is no per-session identity. + // In CI the process-level fake token may still authenticate globally, + // so we check login rather than isAuthenticated. + expect(authStatus.login).toBeFalsy(); + + await session.disconnect(); + } finally { + await noTokenClient.stop(); + } }); it("should error when creating session with invalid token", async () => { @@ -98,3 +113,12 @@ describe("Per-session GitHub auth", async () => { ).rejects.toThrow(/401|Unauthorized/i); }); }); + +function withoutAuthEnv(env: NodeJS.ProcessEnv): NodeJS.ProcessEnv { + return { + ...env, + COPILOT_SDK_AUTH_TOKEN: "", + GH_TOKEN: "", + GITHUB_TOKEN: "", + }; +} diff --git a/nodejs/test/e2e/session_fs.e2e.test.ts b/nodejs/test/e2e/session_fs.e2e.test.ts index a28a2713c..4181152aa 100644 --- a/nodejs/test/e2e/session_fs.e2e.test.ts +++ b/nodejs/test/e2e/session_fs.e2e.test.ts @@ -269,6 +269,16 @@ describe("Session Fs Adapter", () => { async rename(src: string, dest: string): Promise { await provider.rename(src, dest); }, + async sqliteQuery(sessionId, query, queryType, params) { + return { + columns: ["sessionId", "query", "queryType", "answer"], + rows: [{ sessionId, query, queryType, answer: params?.answer }], + rowsAffected: 0, + }; + }, + async sqliteExists(sessionId) { + return sessionId === "handler-session"; + }, }; const handler = createSessionFsAdapter(userProvider); @@ -339,6 +349,25 @@ describe("Session Fs Adapter", () => { const missing = await handler.stat(params({ path: "/workspace/nested/missing.txt" })); expect(missing.error?.code).toBe("ENOENT"); + + const sqliteQuery = await handler.sqliteQuery({ + sessionId, + query: "select :answer as answer", + queryType: "query", + params: { answer: 42 }, + }); + expect(sqliteQuery.columns).toContain("answer"); + expect(sqliteQuery.rows[0]).toMatchObject({ + sessionId, + query: "select :answer as answer", + queryType: "query", + answer: 42, + }); + expect(sqliteQuery.rowsAffected).toBe(0); + expect(sqliteQuery.error).toBeUndefined(); + + const sqliteExists = await handler.sqliteExists({ sessionId }); + expect(sqliteExists.exists).toBe(true); }); it("converts provider exceptions to RPC errors", async () => { @@ -376,6 +405,12 @@ describe("Session Fs Adapter", () => { rename: async () => { throw enoent; }, + sqliteQuery: async () => { + throw enoent; + }, + sqliteExists: async () => { + throw enoent; + }, }; const handler = createSessionFsAdapter(throwing); @@ -410,6 +445,18 @@ describe("Session Fs Adapter", () => { assertEnoent((await handler.readdirWithTypes({ path: "missing-dir" } as never)).error); assertEnoent(await handler.rm({ path: "missing.txt" } as never)); assertEnoent(await handler.rename({ src: "missing.txt", dest: "dest.txt" } as never)); + const sqliteQuery = await handler.sqliteQuery({ + sessionId: "throw-session", + query: "select 1", + queryType: "query", + }); + assertEnoent(sqliteQuery.error); + expect(sqliteQuery.columns).toEqual([]); + expect(sqliteQuery.rows).toEqual([]); + expect(sqliteQuery.rowsAffected).toBe(0); + + const sqliteExistsResult = await handler.sqliteExists({ sessionId: "throw-session" }); + expect(sqliteExistsResult.exists).toBe(false); // Non-ENOENT errors map to UNKNOWN. const unknown: SessionFsProvider = { @@ -508,5 +555,15 @@ function createTestSessionFsHandler( async rename(src: string, dest: string): Promise { await provider.rename(sp(src), sp(dest)); }, + async sqliteQuery() { + return { + columns: [], + rows: [], + rowsAffected: 0, + }; + }, + async sqliteExists(sessionId) { + return sessionId === session.sessionId; + }, }; } diff --git a/nodejs/test/session_fs_adapter.test.ts b/nodejs/test/session_fs_adapter.test.ts index 1c4044c7a..7bed1f8c1 100644 --- a/nodejs/test/session_fs_adapter.test.ts +++ b/nodejs/test/session_fs_adapter.test.ts @@ -59,6 +59,18 @@ describe("SessionFsAdapter", () => { async rename(src, dest) { await memoryProvider.rename(sp(src), sp(dest)); }, + async sqliteQuery(actualSessionId, query, queryType, params) { + return { + columns: ["sessionId", "query", "queryType", "answer"], + rows: [ + { sessionId: actualSessionId, query, queryType, answer: params?.answer }, + ], + rowsAffected: 0, + }; + }, + async sqliteExists(actualSessionId) { + return actualSessionId === sessionId; + }, }; const handler = createSessionFsAdapter(provider); @@ -149,6 +161,25 @@ describe("SessionFsAdapter", () => { path: "/workspace/nested/missing.txt", }); expect(missing.error?.code).toBe("ENOENT"); + + const sqliteResult = await handler.sqliteQuery({ + sessionId, + query: "select :answer as answer", + queryType: "query", + params: { answer: 42 }, + }); + expect(sqliteResult.columns).toContain("answer"); + expect(sqliteResult.rows[0]).toMatchObject({ + sessionId, + query: "select :answer as answer", + queryType: "query", + answer: 42, + }); + expect(sqliteResult.rowsAffected).toBe(0); + expect(sqliteResult.error).toBeUndefined(); + + const sqliteExists = await handler.sqliteExists({ sessionId }); + expect(sqliteExists.exists).toBe(true); }); it("converts provider exceptions to rpc errors", async () => { @@ -172,6 +203,8 @@ describe("SessionFsAdapter", () => { readdirWithTypes: () => Promise.reject(error), rm: () => Promise.reject(error), rename: () => Promise.reject(error), + sqliteQuery: () => Promise.reject(error), + sqliteExists: () => Promise.reject(error), }; } @@ -202,6 +235,16 @@ describe("SessionFsAdapter", () => { assertEnoent((await handler.readdirWithTypes({ sessionId, path: "missing-dir" })).error); assertEnoent(await handler.rm({ sessionId, path: "missing.txt" })); assertEnoent(await handler.rename({ sessionId, src: "missing.txt", dest: "dest.txt" })); + const sqliteQuery = await handler.sqliteQuery({ + sessionId, + query: "select 1", + queryType: "query", + }); + assertEnoent(sqliteQuery.error); + expect(sqliteQuery.columns).toEqual([]); + expect(sqliteQuery.rows).toEqual([]); + expect(sqliteQuery.rowsAffected).toBe(0); + expect((await handler.sqliteExists({ sessionId })).exists).toBe(false); const unknownProvider = createSessionFsAdapter(makeThrowingProvider(makeError("bad path"))); const unknownError = await unknownProvider.writeFile({ diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 00303682c..50b1f8105 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING -from .session_events import EmbeddedBlobResourceContents, EmbeddedTextResourceContents, ReasoningSummary +from .session_events import EmbeddedBlobResourceContents, EmbeddedTextResourceContents, McpServerSource, McpServerStatus, ReasoningSummary, SessionMode, SkillSource if TYPE_CHECKING: from .._jsonrpc import JsonRpcClient @@ -98,7 +98,7 @@ class AccountQuotaSnapshot: """Schema for the `AccountQuotaSnapshot` type.""" entitlement_requests: int - """Number of requests included in the entitlement""" + """Number of requests included in the entitlement, or -1 for unlimited entitlements""" is_unlimited_entitlement: bool """Whether the user has an unlimited usage entitlement""" @@ -450,6 +450,15 @@ def to_dict(self) -> dict: result["owner"] = from_str(self.owner) return result +class ContentFilterMode(Enum): + """Controls how MCP tool result content is filtered: none leaves content unchanged, markdown + sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes + characters that can hide directives. + """ + HIDDEN_CHARACTERS = "hidden_characters" + MARKDOWN = "markdown" + NONE = "none" + @dataclass class CurrentModel: """The currently selected model for the session.""" @@ -469,19 +478,8 @@ def to_dict(self) -> dict: result["modelId"] = from_union([from_str, from_none], self.model_id) return result -# Experimental: this type is part of an experimental API and may change or be removed. -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)""" + """Server transport type: stdio, http, sse, or memory""" HTTP = "http" MEMORY = "memory" @@ -542,6 +540,13 @@ def to_dict(self) -> dict: result["id"] = from_str(self.id) return result +class ExternalToolTextResultForLlmBinaryResultsForLlmType(Enum): + """Binary result type discriminator. Use "image" for images and "resource" for other binary + data. + """ + IMAGE = "image" + RESOURCE = "resource" + class ExternalToolTextResultForLlmContentResourceLinkIconTheme(Enum): """Theme variant this icon is intended for""" @@ -574,15 +579,6 @@ class ExternalToolTextResultForLlmContentTerminalType(Enum): class KindEnum(Enum): TEXT = "text" -class FilterMappingString(Enum): - """Allowed values for the `FilterMappingValue` enumeration. - - Allowed values for the `FilterMappingString` enumeration. - """ - 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 FleetStartRequest: @@ -768,21 +764,36 @@ def to_dict(self) -> dict: result["eventId"] = str(self.event_id) return result +@dataclass +class MCPServerConfigHTTPAuth: + """Additional authentication configuration for this server.""" + + redirect_port: int | None = None + """Fixed port for the OAuth redirect callback server.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigHTTPAuth': + assert isinstance(obj, dict) + redirect_port = from_union([from_int, from_none], obj.get("redirectPort")) + return MCPServerConfigHTTPAuth(redirect_port) + + def to_dict(self) -> dict: + result: dict = {} + if self.redirect_port is not None: + result["redirectPort"] = from_union([from_int, from_none], self.redirect_port) + return result + class MCPServerConfigHTTPOauthGrantType(Enum): """OAuth grant type to use when authenticating to the remote MCP server.""" AUTHORIZATION_CODE = "authorization_code" CLIENT_CREDENTIALS = "client_credentials" -class MCPServerConfigType(Enum): - """Local transport type. Defaults to "local". +class MCPServerConfigHTTPType(Enum): + """Remote transport type. Defaults to "http" when omitted.""" - Remote transport type. Defaults to "http" when omitted. - """ HTTP = "http" - LOCAL = "local" SSE = "sse" - STDIO = "stdio" @dataclass class MCPConfigDisableRequest: @@ -974,36 +985,58 @@ def to_dict(self) -> dict: return result # Experimental: this type is part of an experimental API and may change or be removed. -class MCPServerStatus(Enum): +@dataclass +class MCPServer: + """Schema for the `McpServer` type.""" + + name: str + """Server name (config key)""" + + status: McpServerStatus """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" + error: str | None = None + """Error message if the server failed to connect""" -class MCPServerConfigHTTPType(Enum): - """Remote transport type. Defaults to "http" when omitted.""" + source: McpServerSource | None = None + """Configuration source: user, workspace, plugin, or builtin""" - HTTP = "http" - SSE = "sse" + @staticmethod + def from_dict(obj: Any) -> 'MCPServer': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + status = McpServerStatus(obj.get("status")) + error = from_union([from_str, from_none], obj.get("error")) + source = from_union([McpServerSource, from_none], obj.get("source")) + return MCPServer(name, status, error, source) -class MCPServerConfigLocalType(Enum): - """Local transport type. Defaults to "local".""" + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["status"] = to_enum(McpServerStatus, self.status) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.source is not None: + result["source"] = from_union([lambda x: to_enum(McpServerSource, x), from_none], self.source) + return result - LOCAL = "local" - STDIO = "stdio" +@dataclass +class ModeSetRequest: + """Agent interaction mode to apply to the session.""" -class Mode(Enum): - """The agent mode. Valid values: "interactive", "plan", "autopilot". + mode: SessionMode + """The session mode the agent is operating in""" - Optional target session mode - """ - AUTOPILOT = "autopilot" - INTERACTIVE = "interactive" - PLAN = "plan" + @staticmethod + def from_dict(obj: Any) -> 'ModeSetRequest': + assert isinstance(obj, dict) + mode = SessionMode(obj.get("mode")) + return ModeSetRequest(mode) + + def to_dict(self) -> dict: + result: dict = {} + result["mode"] = to_enum(SessionMode, self.mode) + return result @dataclass class ModelBillingTokenPrices: @@ -1107,29 +1140,12 @@ class ModelPickerPriceCategory(Enum): MEDIUM = "medium" VERY_HIGH = "very_high" -@dataclass -class ModelPolicy: - """Policy state (if applicable)""" - - state: str +class ModelPolicyState(Enum): """Current policy state for this model""" - terms: str | None = None - """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_union([from_str, from_none], obj.get("terms")) - return ModelPolicy(state, terms) - - def to_dict(self) -> dict: - result: dict = {} - result["state"] = from_str(self.state) - if self.terms is not None: - result["terms"] = from_union([from_str, from_none], self.terms) - return result + DISABLED = "disabled" + ENABLED = "enabled" + UNCONFIGURED = "unconfigured" @dataclass class ModelCapabilitiesOverrideLimitsVision: @@ -1628,7 +1644,7 @@ class ServerSkill: name: str """Unique identifier for the skill""" - source: str + source: SkillSource """Source location type (e.g., project, personal-copilot, plugin, builtin)""" user_invocable: bool @@ -1646,7 +1662,7 @@ def from_dict(obj: Any) -> 'ServerSkill': description = from_str(obj.get("description")) enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = from_str(obj.get("source")) + source = SkillSource(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")) @@ -1657,7 +1673,7 @@ def to_dict(self) -> 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["source"] = to_enum(SkillSource, 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) @@ -1924,6 +1940,25 @@ def to_dict(self) -> dict: result["recursive"] = from_union([from_bool, from_none], self.recursive) return result +@dataclass +class SessionFSSetProviderCapabilities: + """Optional capabilities declared by the provider""" + + sqlite: bool | None = None + """Whether the provider supports SQLite query/exists operations""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSetProviderCapabilities': + assert isinstance(obj, dict) + sqlite = from_union([from_bool, from_none], obj.get("sqlite")) + return SessionFSSetProviderCapabilities(sqlite) + + def to_dict(self) -> dict: + result: dict = {} + if self.sqlite is not None: + result["sqlite"] = from_union([from_bool, from_none], self.sqlite) + return result + class SessionFSSetProviderConventions(Enum): """Path conventions used by this filesystem""" @@ -1948,6 +1983,50 @@ def to_dict(self) -> dict: result["success"] = from_bool(self.success) return result +@dataclass +class SessionFSSqliteExistsRequest: + """Identifies the target session.""" + + session_id: str + """Target session identifier""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteExistsRequest': + assert isinstance(obj, dict) + session_id = from_str(obj.get("sessionId")) + return SessionFSSqliteExistsRequest(session_id) + + def to_dict(self) -> dict: + result: dict = {} + result["sessionId"] = from_str(self.session_id) + return result + +@dataclass +class SessionFSSqliteExistsResult: + """Indicates whether the per-session SQLite database already exists.""" + + exists: bool + """Whether the session database already exists""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteExistsResult': + assert isinstance(obj, dict) + exists = from_bool(obj.get("exists")) + return SessionFSSqliteExistsResult(exists) + + def to_dict(self) -> dict: + result: dict = {} + result["exists"] = from_bool(self.exists) + return result + +class SessionFSSqliteQueryType(Enum): + """How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT + (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + """ + EXEC = "exec" + QUERY = "query" + RUN = "run" + @dataclass class SessionFSStatRequest: """Path whose metadata should be returned from the client-provided session filesystem.""" @@ -2153,8 +2232,8 @@ class Skill: name: str """Unique identifier for the skill""" - source: str - """Source location type (e.g., project, personal, plugin)""" + source: SkillSource + """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""" @@ -2168,7 +2247,7 @@ def from_dict(obj: Any) -> 'Skill': description = from_str(obj.get("description")) enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = from_str(obj.get("source")) + source = SkillSource(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) @@ -2178,7 +2257,7 @@ def to_dict(self) -> 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["source"] = to_enum(SkillSource, 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) @@ -2283,16 +2362,14 @@ class SlashCommandInvocationResultKind(Enum): TEXT = "text" # Experimental: this type is part of an experimental API and may change or be removed. -class TaskInfoExecutionMode(Enum): - """How the agent is currently being managed by the runtime +class TaskExecutionMode(Enum): + """Whether task execution is synchronously awaited or managed in the background""" - Whether the shell command is currently sync-waited or background-managed - """ BACKGROUND = "background" SYNC = "sync" # Experimental: this type is part of an experimental API and may change or be removed. -class TaskInfoStatus(Enum): +class TaskStatus(Enum): """Current lifecycle status of the task""" CANCELLED = "cancelled" @@ -3125,7 +3202,7 @@ class ConnectedRemoteSessionMetadata: kind: ConnectedRemoteSessionMetadataKind """Neutral SDK discriminator for the connected remote session kind.""" - modified_time: str + modified_time: datetime """Last session update time as an ISO 8601 string.""" repository: ConnectedRemoteSessionMetadataRepository @@ -3134,7 +3211,7 @@ class ConnectedRemoteSessionMetadata: session_id: str """SDK session ID for the connected remote session.""" - start_time: str + start_time: datetime """Session start time as an ISO 8601 string.""" name: str | None = None @@ -3146,7 +3223,7 @@ class ConnectedRemoteSessionMetadata: resource_id: str | None = None """Original remote resource identifier.""" - stale_at: str | None = None + stale_at: datetime | None = None """Remote session staleness deadline as an ISO 8601 string.""" state: str | None = None @@ -3159,14 +3236,14 @@ class ConnectedRemoteSessionMetadata: def from_dict(obj: Any) -> 'ConnectedRemoteSessionMetadata': assert isinstance(obj, dict) kind = ConnectedRemoteSessionMetadataKind(obj.get("kind")) - modified_time = from_str(obj.get("modifiedTime")) + modified_time = from_datetime(obj.get("modifiedTime")) repository = ConnectedRemoteSessionMetadataRepository.from_dict(obj.get("repository")) session_id = from_str(obj.get("sessionId")) - start_time = from_str(obj.get("startTime")) + start_time = from_datetime(obj.get("startTime")) name = from_union([from_str, from_none], obj.get("name")) pull_request_number = from_union([from_int, from_none], obj.get("pullRequestNumber")) resource_id = from_union([from_str, from_none], obj.get("resourceId")) - stale_at = from_union([from_str, from_none], obj.get("staleAt")) + stale_at = from_union([from_datetime, from_none], obj.get("staleAt")) state = from_union([from_str, from_none], obj.get("state")) summary = from_union([from_str, from_none], obj.get("summary")) return ConnectedRemoteSessionMetadata(kind, modified_time, repository, session_id, start_time, name, pull_request_number, resource_id, stale_at, state, summary) @@ -3174,10 +3251,10 @@ def from_dict(obj: Any) -> 'ConnectedRemoteSessionMetadata': def to_dict(self) -> dict: result: dict = {} result["kind"] = to_enum(ConnectedRemoteSessionMetadataKind, self.kind) - result["modifiedTime"] = from_str(self.modified_time) + result["modifiedTime"] = self.modified_time.isoformat() result["repository"] = to_class(ConnectedRemoteSessionMetadataRepository, self.repository) result["sessionId"] = from_str(self.session_id) - result["startTime"] = from_str(self.start_time) + result["startTime"] = self.start_time.isoformat() if self.name is not None: result["name"] = from_union([from_str, from_none], self.name) if self.pull_request_number is not None: @@ -3185,13 +3262,75 @@ def to_dict(self) -> dict: if self.resource_id is not None: result["resourceId"] = from_union([from_str, from_none], self.resource_id) if self.stale_at is not None: - result["staleAt"] = from_union([from_str, from_none], self.stale_at) + result["staleAt"] = from_union([lambda x: x.isoformat(), from_none], self.stale_at) if self.state is not None: result["state"] = from_union([from_str, from_none], self.state) if self.summary is not None: result["summary"] = from_union([from_str, from_none], self.summary) return result +@dataclass +class MCPServerConfigStdio: + """Stdio MCP server configuration launched as a child process.""" + + command: str + """Executable command used to start the Stdio MCP server process.""" + + args: list[str] | None = None + """Command-line arguments passed to the Stdio MCP server process.""" + + cwd: str | None = None + """Working directory for the Stdio MCP server process.""" + + env: dict[str, str] | None = None + """Environment variables to pass to the Stdio MCP server process.""" + + filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode | None = None + """Content filtering mode to apply to all tools, or a map of tool name to content filtering + mode. + """ + is_default_server: bool | None = None + """Whether this server is a built-in fallback used when the user has not configured their + own server. + """ + 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.""" + + @staticmethod + def from_dict(obj: Any) -> 'MCPServerConfigStdio': + assert isinstance(obj, dict) + command = from_str(obj.get("command")) + args = from_union([lambda x: from_list(from_str, x), from_none], obj.get("args")) + 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(ContentFilterMode, x), ContentFilterMode, 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")) + return MCPServerConfigStdio(command, args, cwd, env, filter_mapping, is_default_server, timeout, tools) + + def to_dict(self) -> dict: + result: dict = {} + result["command"] = from_str(self.command) + if self.args is not None: + result["args"] = from_union([lambda x: from_list(from_str, x), from_none], self.args) + 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(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, 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) + return result + @dataclass class DiscoveredMCPServer: """Schema for the `DiscoveredMcpServer` type.""" @@ -3202,18 +3341,18 @@ class DiscoveredMCPServer: name: str """Server name (config key)""" - source: MCPServerSource - """Configuration source""" + source: McpServerSource + """Configuration source: user, workspace, plugin, or builtin""" type: DiscoveredMCPServerType | None = None - """Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio)""" + """Server transport type: stdio, http, sse, or memory""" @staticmethod def from_dict(obj: Any) -> 'DiscoveredMCPServer': assert isinstance(obj, dict) enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = MCPServerSource(obj.get("source")) + source = McpServerSource(obj.get("source")) type = from_union([DiscoveredMCPServerType, from_none], obj.get("type")) return DiscoveredMCPServer(enabled, name, source, type) @@ -3221,7 +3360,7 @@ 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) + 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) return result @@ -3266,6 +3405,41 @@ def to_dict(self) -> dict: result["pid"] = from_union([from_int, from_none], self.pid) return result +@dataclass +class ExternalToolTextResultForLlmBinaryResultsForLlm: + """Binary result returned by a tool for the model""" + + data: str + """Base64-encoded binary data""" + + mime_type: str + """MIME type of the binary data""" + + type: ExternalToolTextResultForLlmBinaryResultsForLlmType + """Binary result type discriminator. Use "image" for images and "resource" for other binary + data. + """ + description: str | None = None + """Human-readable description of the binary data""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmBinaryResultsForLlm': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + mime_type = from_str(obj.get("mimeType")) + type = ExternalToolTextResultForLlmBinaryResultsForLlmType(obj.get("type")) + description = from_union([from_str, from_none], obj.get("description")) + return ExternalToolTextResultForLlmBinaryResultsForLlm(data, mime_type, type, description) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["mimeType"] = from_str(self.mime_type) + result["type"] = to_enum(ExternalToolTextResultForLlmBinaryResultsForLlmType, self.type) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + return result + @dataclass class ExternalToolTextResultForLlmContentResourceLinkIcon: """Icon image for a resource""" @@ -3614,25 +3788,25 @@ def to_dict(self) -> dict: @dataclass class MCPServerConfig: - """MCP server configuration (local/stdio or remote/http) + """MCP server configuration (stdio process or remote HTTP/SSE) - Local MCP server configuration launched as a child process. + Stdio MCP server configuration launched as a child process. Remote MCP server configuration accessed over HTTP or SSE. """ args: list[str] | None = None - """Command-line arguments passed to the local MCP server process.""" + """Command-line arguments passed to the Stdio MCP server process.""" command: str | None = None - """Executable command used to start the local MCP server process.""" + """Executable command used to start the Stdio MCP server process.""" cwd: str | None = None - """Working directory for the local MCP server process.""" + """Working directory for the Stdio MCP server process.""" env: dict[str, str] | None = None - """Environment variables to pass to the local MCP server process.""" + """Environment variables to pass to the Stdio MCP server process.""" - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode | None = None """Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. """ @@ -3646,11 +3820,9 @@ class MCPServerConfig: tools: list[str] | None = None """Tools to include. Defaults to all tools if not specified.""" - type: MCPServerConfigType | None = None - """Local transport type. Defaults to "local". + auth: MCPServerConfigHTTPAuth | None = None + """Additional authentication configuration for this server.""" - Remote transport type. Defaults to "http" when omitted. - """ headers: dict[str, str] | None = None """HTTP headers to include in requests to the remote MCP server.""" @@ -3663,6 +3835,9 @@ class MCPServerConfig: oauth_public_client: bool | None = None """Whether the configured OAuth client is public and does not require a client secret.""" + type: MCPServerConfigHTTPType | None = None + """Remote transport type. Defaults to "http" when omitted.""" + url: str | None = None """URL of the remote MCP server endpoint.""" @@ -3673,17 +3848,18 @@ def from_dict(obj: Any) -> 'MCPServerConfig': 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")) + filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode, 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")) + auth = from_union([MCPServerConfigHTTPAuth.from_dict, from_none], obj.get("auth")) 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_grant_type = from_union([MCPServerConfigHTTPOauthGrantType, from_none], obj.get("oauthGrantType")) oauth_public_client = from_union([from_bool, from_none], obj.get("oauthPublicClient")) + type = from_union([MCPServerConfigHTTPType, from_none], obj.get("type")) 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_grant_type, oauth_public_client, url) + return MCPServerConfig(args, command, cwd, env, filter_mapping, is_default_server, timeout, tools, auth, headers, oauth_client_id, oauth_grant_type, oauth_public_client, type, url) def to_dict(self) -> dict: result: dict = {} @@ -3696,15 +3872,15 @@ def to_dict(self) -> dict: 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) + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, 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.auth is not None: + result["auth"] = from_union([lambda x: to_class(MCPServerConfigHTTPAuth, x), from_none], self.auth) 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: @@ -3713,46 +3889,12 @@ def to_dict(self) -> dict: result["oauthGrantType"] = from_union([lambda x: to_enum(MCPServerConfigHTTPOauthGrantType, x), from_none], self.oauth_grant_type) if self.oauth_public_client is not None: result["oauthPublicClient"] = from_union([from_bool, from_none], self.oauth_public_client) + if self.type is not None: + result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) 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 MCPServer: - """Schema for the `McpServer` type.""" - - name: str - """Server name (config key)""" - - status: MCPServerStatus - """Connection status: connected, failed, needs-auth, pending, disabled, or not_configured""" - - error: str | None = None - """Error message if the server failed to connect""" - - source: MCPServerSource | None = None - """Configuration source: user, workspace, plugin, or builtin""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServer': - assert isinstance(obj, dict) - name = from_str(obj.get("name")) - status = MCPServerStatus(obj.get("status")) - error = from_union([from_str, from_none], obj.get("error")) - source = from_union([MCPServerSource, from_none], obj.get("source")) - return MCPServer(name, status, error, source) - - def to_dict(self) -> dict: - result: dict = {} - result["name"] = from_str(self.name) - result["status"] = to_enum(MCPServerStatus, self.status) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.source is not None: - result["source"] = from_union([lambda x: to_enum(MCPServerSource, x), from_none], self.source) - return result - @dataclass class MCPServerConfigHTTP: """Remote MCP server configuration accessed over HTTP or SSE.""" @@ -3760,7 +3902,10 @@ class MCPServerConfigHTTP: url: str """URL of the remote MCP server endpoint.""" - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None + auth: MCPServerConfigHTTPAuth | None = None + """Additional authentication configuration for this server.""" + + filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode | None = None """Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. """ @@ -3793,7 +3938,8 @@ class MCPServerConfigHTTP: def from_dict(obj: Any) -> 'MCPServerConfigHTTP': assert isinstance(obj, dict) url = from_str(obj.get("url")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString, from_none], obj.get("filterMapping")) + auth = from_union([MCPServerConfigHTTPAuth.from_dict, from_none], obj.get("auth")) + filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode, 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")) @@ -3802,13 +3948,15 @@ def from_dict(obj: Any) -> 'MCPServerConfigHTTP': 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_grant_type, oauth_public_client, timeout, tools, type) + return MCPServerConfigHTTP(url, auth, filter_mapping, headers, is_default_server, oauth_client_id, oauth_grant_type, oauth_public_client, timeout, tools, type) def to_dict(self) -> dict: result: dict = {} result["url"] = from_str(self.url) + if self.auth is not None: + result["auth"] = from_union([lambda x: to_class(MCPServerConfigHTTPAuth, x), from_none], self.auth) 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) + result["filterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, 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: @@ -3827,89 +3975,23 @@ def to_dict(self) -> dict: result["type"] = from_union([lambda x: to_enum(MCPServerConfigHTTPType, x), from_none], self.type) return result +# Experimental: this type is part of an experimental API and may change or be removed. @dataclass -class MCPServerConfigLocal: - """Local MCP server configuration launched as a child process.""" - - args: list[str] - """Command-line arguments passed to the local MCP server process.""" - - command: str - """Executable command used to start the local MCP server process.""" - - cwd: str | None = None - """Working directory for the local MCP server process.""" - - env: dict[str, str] | None = None - """Environment variables to pass to the local MCP server process.""" - - filter_mapping: dict[str, FilterMappingString] | FilterMappingString | None = None - """Content filtering mode to apply to all tools, or a map of tool name to content filtering - mode. - """ - is_default_server: bool | None = None - """Whether this server is a built-in fallback used when the user has not configured their - own server. - """ - 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 - """Local transport type. Defaults to "local".""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerConfigLocal': - assert isinstance(obj, dict) - 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["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 ModeSetRequest: - """Agent interaction mode to apply to the session.""" +class MCPServerList: + """MCP servers configured for the session, with their connection status.""" - mode: Mode - """The agent mode. Valid values: "interactive", "plan", "autopilot".""" + servers: list[MCPServer] + """Configured MCP servers""" @staticmethod - def from_dict(obj: Any) -> 'ModeSetRequest': + def from_dict(obj: Any) -> 'MCPServerList': assert isinstance(obj, dict) - mode = Mode(obj.get("mode")) - return ModeSetRequest(mode) + servers = from_list(MCPServer.from_dict, obj.get("servers")) + return MCPServerList(servers) def to_dict(self) -> dict: result: dict = {} - result["mode"] = to_enum(Mode, self.mode) + result["servers"] = from_list(lambda x: to_class(MCPServer, x), self.servers) return result @dataclass @@ -3974,6 +4056,30 @@ def to_dict(self) -> dict: result["vision"] = from_union([lambda x: to_class(ModelCapabilitiesLimitsVision, x), from_none], self.vision) return result +@dataclass +class ModelPolicy: + """Policy state (if applicable)""" + + state: ModelPolicyState + """Current policy state for this model""" + + terms: str | None = None + """Usage terms or conditions for this model""" + + @staticmethod + def from_dict(obj: Any) -> 'ModelPolicy': + assert isinstance(obj, dict) + state = ModelPolicyState(obj.get("state")) + terms = from_union([from_str, from_none], obj.get("terms")) + return ModelPolicy(state, terms) + + def to_dict(self) -> dict: + result: dict = {} + result["state"] = to_enum(ModelPolicyState, self.state) + if self.terms is not None: + result["terms"] = from_union([from_str, from_none], self.terms) + return result + @dataclass class ModelCapabilitiesOverrideLimits: """Token limits for prompts, outputs, and context window""" @@ -4599,19 +4705,61 @@ class SessionFSSetProviderRequest: session_state_path: str """Path within each session's SessionFs where the runtime stores files for that session""" + capabilities: SessionFSSetProviderCapabilities | None = None + """Optional capabilities declared by the provider""" + @staticmethod def from_dict(obj: Any) -> 'SessionFSSetProviderRequest': 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) + capabilities = from_union([SessionFSSetProviderCapabilities.from_dict, from_none], obj.get("capabilities")) + return SessionFSSetProviderRequest(conventions, initial_cwd, session_state_path, capabilities) 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) + if self.capabilities is not None: + result["capabilities"] = from_union([lambda x: to_class(SessionFSSetProviderCapabilities, x), from_none], self.capabilities) + return result + +@dataclass +class SessionFSSqliteQueryRequest: + """SQL query, query type, and optional bind parameters for executing a SQLite query against + the per-session database. + """ + query: str + """SQL query to execute""" + + query_type: SessionFSSqliteQueryType + """How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT + (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + """ + session_id: str + """Target session identifier""" + + params: dict[str, float | str | None] | None = None + """Optional named bind parameters""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteQueryRequest': + assert isinstance(obj, dict) + query = from_str(obj.get("query")) + query_type = SessionFSSqliteQueryType(obj.get("queryType")) + session_id = from_str(obj.get("sessionId")) + params = from_union([lambda x: from_dict(lambda x: from_union([from_none, from_float, from_str], x), x), from_none], obj.get("params")) + return SessionFSSqliteQueryRequest(query, query_type, session_id, params) + + def to_dict(self) -> dict: + result: dict = {} + result["query"] = from_str(self.query) + result["queryType"] = to_enum(SessionFSSqliteQueryType, self.query_type) + result["sessionId"] = from_str(self.session_id) + if self.params is not None: + result["params"] = from_union([lambda x: from_dict(lambda x: from_union([from_none, to_float, from_str], x), x), from_none], self.params) return result @dataclass @@ -4688,8 +4836,8 @@ class SlashCommandAgentPromptResult: prompt: str """Prompt to submit to the agent""" - mode: Mode | None = None - """Optional target session mode""" + mode: SessionMode | None = None + """Optional target session mode for the agent prompt""" runtime_settings_changed: bool | None = None """True when the invocation mutated user runtime settings; consumers caching settings should @@ -4702,7 +4850,7 @@ def from_dict(obj: Any) -> 'SlashCommandAgentPromptResult': display_prompt = from_str(obj.get("displayPrompt")) kind = SlashCommandAgentPromptResultKind(obj.get("kind")) prompt = from_str(obj.get("prompt")) - mode = from_union([Mode, from_none], obj.get("mode")) + mode = from_union([SessionMode, from_none], obj.get("mode")) runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) return SlashCommandAgentPromptResult(display_prompt, kind, prompt, mode, runtime_settings_changed) @@ -4712,7 +4860,7 @@ def to_dict(self) -> dict: result["kind"] = to_enum(SlashCommandAgentPromptResultKind, self.kind) result["prompt"] = from_str(self.prompt) if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode) + result["mode"] = from_union([lambda x: to_enum(SessionMode, x), from_none], self.mode) if self.runtime_settings_changed is not None: result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed) return result @@ -4770,7 +4918,7 @@ class TaskShellInfo: started_at: datetime """ISO 8601 timestamp when the task was started""" - status: TaskInfoStatus + status: TaskStatus """Current lifecycle status of the task""" type: TaskShellInfoType @@ -4782,8 +4930,8 @@ class TaskShellInfo: completed_at: datetime | None = None """ISO 8601 timestamp when the task finished""" - execution_mode: TaskInfoExecutionMode | None = None - """Whether the shell command is currently sync-waited or background-managed""" + execution_mode: TaskExecutionMode | None = None + """Whether task execution is synchronously awaited or managed in the background""" log_path: str | None = None """Path to the detached shell log, when available""" @@ -4799,11 +4947,11 @@ def from_dict(obj: Any) -> 'TaskShellInfo': description = from_str(obj.get("description")) id = from_str(obj.get("id")) started_at = from_datetime(obj.get("startedAt")) - status = TaskInfoStatus(obj.get("status")) + status = TaskStatus(obj.get("status")) type = TaskShellInfoType(obj.get("type")) can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground")) completed_at = from_union([from_datetime, from_none], obj.get("completedAt")) - execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode")) + execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode")) log_path = from_union([from_str, from_none], obj.get("logPath")) pid = from_union([from_int, from_none], obj.get("pid")) return TaskShellInfo(attachment_mode, command, description, id, started_at, status, type, can_promote_to_background, completed_at, execution_mode, log_path, pid) @@ -4815,14 +4963,14 @@ def to_dict(self) -> dict: result["description"] = from_str(self.description) result["id"] = from_str(self.id) result["startedAt"] = self.started_at.isoformat() - result["status"] = to_enum(TaskInfoStatus, self.status) + result["status"] = to_enum(TaskStatus, self.status) result["type"] = to_enum(TaskShellInfoType, self.type) if self.can_promote_to_background is not None: result["canPromoteToBackground"] = from_union([from_bool, from_none], self.can_promote_to_background) if self.completed_at is not None: result["completedAt"] = from_union([lambda x: x.isoformat(), from_none], self.completed_at) if self.execution_mode is not None: - result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode) + result["executionMode"] = from_union([lambda x: to_enum(TaskExecutionMode, x), from_none], self.execution_mode) if self.log_path is not None: result["logPath"] = from_union([from_str, from_none], self.log_path) if self.pid is not None: @@ -5633,7 +5781,7 @@ class MCPConfigAddRequest: """MCP server name and configuration to add to user configuration.""" config: MCPServerConfig - """MCP server configuration (local/stdio or remote/http)""" + """MCP server configuration (stdio process or remote HTTP/SSE)""" name: str """Unique name for the MCP server""" @@ -5674,7 +5822,7 @@ class MCPConfigUpdateRequest: """MCP server name and replacement configuration to write to user configuration.""" config: MCPServerConfig - """MCP server configuration (local/stdio or remote/http)""" + """MCP server configuration (stdio process or remote HTTP/SSE)""" name: str """Name of the MCP server to update""" @@ -5692,25 +5840,6 @@ def to_dict(self) -> dict: result["name"] = from_str(self.name) return result -# Experimental: this type is part of an experimental API and may change or be removed. -@dataclass -class MCPServerList: - """MCP servers configured for the session, with their connection status.""" - - servers: list[MCPServer] - """Configured MCP servers""" - - @staticmethod - def from_dict(obj: Any) -> 'MCPServerList': - assert isinstance(obj, dict) - 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(MCPServer, x), self.servers) - return result - @dataclass class ModelCapabilitiesOverride: """Override individual model capabilities resolved by the runtime""" @@ -5807,6 +5936,47 @@ def to_dict(self) -> dict: result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) return result +@dataclass +class SessionFSSqliteQueryResult: + """Query results including rows, columns, and rows affected, or a filesystem error if + execution failed. + """ + columns: list[str] + """Column names from the result set""" + + rows: list[dict[str, Any]] + """For SELECT: array of row objects. For others: empty array.""" + + rows_affected: int + """Number of rows affected (for INSERT/UPDATE/DELETE)""" + + error: SessionFSError | None = None + """Describes a filesystem error.""" + + last_insert_rowid: float | None = None + """Last inserted row ID (for INSERT)""" + + @staticmethod + def from_dict(obj: Any) -> 'SessionFSSqliteQueryResult': + assert isinstance(obj, dict) + columns = from_list(from_str, obj.get("columns")) + rows = from_list(lambda x: from_dict(lambda x: x, x), obj.get("rows")) + rows_affected = from_int(obj.get("rowsAffected")) + error = from_union([SessionFSError.from_dict, from_none], obj.get("error")) + last_insert_rowid = from_union([from_float, from_none], obj.get("lastInsertRowid")) + return SessionFSSqliteQueryResult(columns, rows, rows_affected, error, last_insert_rowid) + + def to_dict(self) -> dict: + result: dict = {} + result["columns"] = from_list(from_str, self.columns) + result["rows"] = from_list(lambda x: from_dict(lambda x: x, x), self.rows) + result["rowsAffected"] = from_int(self.rows_affected) + if self.error is not None: + result["error"] = from_union([lambda x: to_class(SessionFSError, x), from_none], self.error) + if self.last_insert_rowid is not None: + result["lastInsertRowid"] = from_union([to_float, from_none], self.last_insert_rowid) + return result + @dataclass class SessionFSStatResult: """Filesystem metadata for the requested path, or a filesystem error if the stat failed.""" @@ -5910,8 +6080,8 @@ class SlashCommandInvocationResult: display_prompt: str | None = None """Prompt text to display to the user""" - mode: Mode | None = None - """Optional target session mode""" + mode: SessionMode | None = None + """Optional target session mode for the agent prompt""" prompt: str | None = None """Prompt to submit to the agent""" @@ -5928,7 +6098,7 @@ def from_dict(obj: Any) -> 'SlashCommandInvocationResult': runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged")) text = from_union([from_str, from_none], obj.get("text")) display_prompt = from_union([from_str, from_none], obj.get("displayPrompt")) - mode = from_union([Mode, from_none], obj.get("mode")) + mode = from_union([SessionMode, from_none], obj.get("mode")) prompt = from_union([from_str, from_none], obj.get("prompt")) message = from_union([from_str, from_none], obj.get("message")) return SlashCommandInvocationResult(kind, markdown, preserve_ansi, runtime_settings_changed, text, display_prompt, mode, prompt, message) @@ -5947,7 +6117,7 @@ def to_dict(self) -> dict: if self.display_prompt is not None: result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt) if self.mode is not None: - result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode) + result["mode"] = from_union([lambda x: to_enum(SessionMode, x), from_none], self.mode) if self.prompt is not None: result["prompt"] = from_union([from_str, from_none], self.prompt) if self.message is not None: @@ -6606,6 +6776,9 @@ class ExternalToolTextResultForLlm: text_result_for_llm: str """Text result returned to the model""" + binary_results_for_llm: list[ExternalToolTextResultForLlmBinaryResultsForLlm] | None = None + """Base64-encoded binary results returned to the model""" + contents: list[ExternalToolTextResultForLlmContent] | None = None """Structured content blocks from the tool""" @@ -6626,16 +6799,19 @@ class ExternalToolTextResultForLlm: def from_dict(obj: Any) -> 'ExternalToolTextResultForLlm': assert isinstance(obj, dict) text_result_for_llm = from_str(obj.get("textResultForLlm")) + binary_results_for_llm = from_union([lambda x: from_list(ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict, x), from_none], obj.get("binaryResultsForLlm")) contents = from_union([lambda x: from_list(ExternalToolTextResultForLlmContent.from_dict, x), from_none], obj.get("contents")) error = from_union([from_str, from_none], obj.get("error")) result_type = from_union([from_str, from_none], obj.get("resultType")) session_log = from_union([from_str, from_none], obj.get("sessionLog")) tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ExternalToolTextResultForLlm(text_result_for_llm, contents, error, result_type, session_log, tool_telemetry) + return ExternalToolTextResultForLlm(text_result_for_llm, binary_results_for_llm, contents, error, result_type, session_log, tool_telemetry) def to_dict(self) -> dict: result: dict = {} result["textResultForLlm"] = from_str(self.text_result_for_llm) + if self.binary_results_for_llm is not None: + result["binaryResultsForLlm"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, x), x), from_none], self.binary_results_for_llm) if self.contents is not None: result["contents"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmContent, x), x), from_none], self.contents) if self.error is not None: @@ -7017,7 +7193,7 @@ def from_dict(obj: Any) -> 'ModelSwitchToRequest': model_id = from_str(obj.get("modelId")) model_capabilities = from_union([ModelCapabilitiesOverride.from_dict, from_none], obj.get("modelCapabilities")) reasoning_effort = from_union([from_str, from_none], obj.get("reasoningEffort")) - reasoning_summary = from_union([ReasoningSummary.from_dict, from_none], obj.get("reasoningSummary")) + reasoning_summary = from_union([ReasoningSummary, from_none], obj.get("reasoningSummary")) return ModelSwitchToRequest(model_id, model_capabilities, reasoning_effort, reasoning_summary) def to_dict(self) -> dict: @@ -7028,7 +7204,7 @@ def to_dict(self) -> dict: if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_str, from_none], self.reasoning_effort) if self.reasoning_summary is not None: - result["reasoningSummary"] = from_union([lambda x: to_class(ReasoningSummary, x), from_none], self.reasoning_summary) + result["reasoningSummary"] = from_union([lambda x: to_enum(ReasoningSummary, x), from_none], self.reasoning_summary) return result # Experimental: this type is part of an experimental API and may change or be removed. @@ -7051,7 +7227,7 @@ class TaskAgentInfo: started_at: datetime """ISO 8601 timestamp when the task was started""" - status: TaskInfoStatus + status: TaskStatus """Current lifecycle status of the task""" tool_call_id: str @@ -7077,8 +7253,8 @@ class TaskAgentInfo: error: str | None = None """Error message when the task failed""" - execution_mode: TaskInfoExecutionMode | None = None - """How the agent is currently being managed by the runtime""" + execution_mode: TaskExecutionMode | None = None + """Whether task execution is synchronously awaited or managed in the background""" idle_since: datetime | None = None """ISO 8601 timestamp when the agent entered idle state""" @@ -7100,7 +7276,7 @@ def from_dict(obj: Any) -> 'TaskAgentInfo': id = from_str(obj.get("id")) prompt = from_str(obj.get("prompt")) started_at = from_datetime(obj.get("startedAt")) - status = TaskInfoStatus(obj.get("status")) + status = TaskStatus(obj.get("status")) tool_call_id = from_str(obj.get("toolCallId")) type = TaskAgentInfoType(obj.get("type")) active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt")) @@ -7108,7 +7284,7 @@ def from_dict(obj: Any) -> 'TaskAgentInfo': can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground")) completed_at = from_union([from_datetime, from_none], obj.get("completedAt")) error = from_union([from_str, from_none], obj.get("error")) - execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode")) + execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode")) idle_since = from_union([from_datetime, from_none], obj.get("idleSince")) latest_response = from_union([from_str, from_none], obj.get("latestResponse")) model = from_union([from_str, from_none], obj.get("model")) @@ -7122,7 +7298,7 @@ def to_dict(self) -> dict: result["id"] = from_str(self.id) result["prompt"] = from_str(self.prompt) result["startedAt"] = self.started_at.isoformat() - result["status"] = to_enum(TaskInfoStatus, self.status) + result["status"] = to_enum(TaskStatus, self.status) result["toolCallId"] = from_str(self.tool_call_id) result["type"] = to_enum(TaskAgentInfoType, self.type) if self.active_started_at is not None: @@ -7136,7 +7312,7 @@ def to_dict(self) -> dict: if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.execution_mode is not None: - result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode) + result["executionMode"] = from_union([lambda x: to_enum(TaskExecutionMode, x), from_none], self.execution_mode) if self.idle_since is not None: result["idleSince"] = from_union([lambda x: x.isoformat(), from_none], self.idle_since) if self.latest_response is not None: @@ -7165,7 +7341,7 @@ class TaskInfo: started_at: datetime """ISO 8601 timestamp when the task was started""" - status: TaskInfoStatus + status: TaskStatus """Current lifecycle status of the task""" type: TaskInfoType @@ -7193,11 +7369,9 @@ class TaskInfo: error: str | None = None """Error message when the task failed""" - execution_mode: TaskInfoExecutionMode | None = None - """How the agent is currently being managed by the runtime + execution_mode: TaskExecutionMode | None = None + """Whether task execution is synchronously awaited or managed in the background""" - Whether the shell command is currently sync-waited or background-managed - """ idle_since: datetime | None = None """ISO 8601 timestamp when the agent entered idle state""" @@ -7235,7 +7409,7 @@ def from_dict(obj: Any) -> 'TaskInfo': description = from_str(obj.get("description")) id = from_str(obj.get("id")) started_at = from_datetime(obj.get("startedAt")) - status = TaskInfoStatus(obj.get("status")) + status = TaskStatus(obj.get("status")) type = TaskInfoType(obj.get("type")) active_started_at = from_union([from_datetime, from_none], obj.get("activeStartedAt")) active_time_ms = from_union([from_int, from_none], obj.get("activeTimeMs")) @@ -7243,7 +7417,7 @@ def from_dict(obj: Any) -> 'TaskInfo': can_promote_to_background = from_union([from_bool, from_none], obj.get("canPromoteToBackground")) completed_at = from_union([from_datetime, from_none], obj.get("completedAt")) error = from_union([from_str, from_none], obj.get("error")) - execution_mode = from_union([TaskInfoExecutionMode, from_none], obj.get("executionMode")) + execution_mode = from_union([TaskExecutionMode, from_none], obj.get("executionMode")) idle_since = from_union([from_datetime, from_none], obj.get("idleSince")) latest_response = from_union([from_str, from_none], obj.get("latestResponse")) model = from_union([from_str, from_none], obj.get("model")) @@ -7261,7 +7435,7 @@ def to_dict(self) -> dict: result["description"] = from_str(self.description) result["id"] = from_str(self.id) result["startedAt"] = self.started_at.isoformat() - result["status"] = to_enum(TaskInfoStatus, self.status) + result["status"] = to_enum(TaskStatus, self.status) result["type"] = to_enum(TaskInfoType, self.type) if self.active_started_at is not None: result["activeStartedAt"] = from_union([lambda x: x.isoformat(), from_none], self.active_started_at) @@ -7276,7 +7450,7 @@ def to_dict(self) -> dict: if self.error is not None: result["error"] = from_union([from_str, from_none], self.error) if self.execution_mode is not None: - result["executionMode"] = from_union([lambda x: to_enum(TaskInfoExecutionMode, x), from_none], self.execution_mode) + result["executionMode"] = from_union([lambda x: to_enum(TaskExecutionMode, x), from_none], self.execution_mode) if self.idle_since is not None: result["idleSince"] = from_union([lambda x: x.isoformat(), from_none], self.idle_since) if self.latest_response is not None: @@ -7343,9 +7517,9 @@ class RPC: connect_remote_session_params: ConnectRemoteSessionParams connect_request: ConnectRequest connect_result: ConnectResult + content_filter_mode: ContentFilterMode current_model: CurrentModel discovered_mcp_server: DiscoveredMCPServer - discovered_mcp_server_source: MCPServerSource discovered_mcp_server_type: DiscoveredMCPServerType extension: Extension extension_list: ExtensionList @@ -7355,6 +7529,8 @@ class RPC: extension_status: ExtensionStatus external_tool_result: ExternalToolTextResultForLlm | str external_tool_text_result_for_llm: ExternalToolTextResultForLlm + external_tool_text_result_for_llm_binary_results_for_llm: ExternalToolTextResultForLlmBinaryResultsForLlm + external_tool_text_result_for_llm_binary_results_for_llm_type: ExternalToolTextResultForLlmBinaryResultsForLlmType external_tool_text_result_for_llm_content: ExternalToolTextResultForLlmContent external_tool_text_result_for_llm_content_audio: ExternalToolTextResultForLlmContentAudio external_tool_text_result_for_llm_content_image: ExternalToolTextResultForLlmContentImage @@ -7365,9 +7541,7 @@ class RPC: external_tool_text_result_for_llm_content_resource_link_icon_theme: ExternalToolTextResultForLlmContentResourceLinkIconTheme external_tool_text_result_for_llm_content_terminal: ExternalToolTextResultForLlmContentTerminal external_tool_text_result_for_llm_content_text: ExternalToolTextResultForLlmContentText - filter_mapping: dict[str, FilterMappingString] | FilterMappingString - filter_mapping_string: FilterMappingString - filter_mapping_value: FilterMappingString + filter_mapping: dict[str, ContentFilterMode] | ContentFilterMode fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult handle_pending_tool_call_request: HandlePendingToolCallRequest @@ -7397,13 +7571,11 @@ class RPC: mcp_server: MCPServer mcp_server_config: MCPServerConfig mcp_server_config_http: MCPServerConfigHTTP + mcp_server_config_http_auth: MCPServerConfigHTTPAuth mcp_server_config_http_oauth_grant_type: MCPServerConfigHTTPOauthGrantType mcp_server_config_http_type: MCPServerConfigHTTPType - mcp_server_config_local: MCPServerConfigLocal - mcp_server_config_local_type: MCPServerConfigLocalType + mcp_server_config_stdio: MCPServerConfigStdio mcp_server_list: MCPServerList - mcp_server_source: MCPServerSource - mcp_server_status: MCPServerStatus model: Model model_billing: ModelBilling model_billing_token_prices: ModelBillingTokenPrices @@ -7419,6 +7591,7 @@ class RPC: model_picker_category: ModelPickerCategory model_picker_price_category: ModelPickerPriceCategory model_policy: ModelPolicy + model_policy_state: ModelPolicyState models_list_request: ModelsListRequest model_switch_to_request: ModelSwitchToRequest model_switch_to_result: ModelSwitchToResult @@ -7490,14 +7663,20 @@ class RPC: session_fs_read_file_result: SessionFSReadFileResult session_fs_rename_request: SessionFSRenameRequest session_fs_rm_request: SessionFSRmRequest + session_fs_set_provider_capabilities: SessionFSSetProviderCapabilities session_fs_set_provider_conventions: SessionFSSetProviderConventions session_fs_set_provider_request: SessionFSSetProviderRequest session_fs_set_provider_result: SessionFSSetProviderResult + session_fs_sqlite_exists_request: SessionFSSqliteExistsRequest + session_fs_sqlite_exists_result: SessionFSSqliteExistsResult + session_fs_sqlite_query_request: SessionFSSqliteQueryRequest + session_fs_sqlite_query_result: SessionFSSqliteQueryResult + session_fs_sqlite_query_type: SessionFSSqliteQueryType session_fs_stat_request: SessionFSStatRequest session_fs_stat_result: SessionFSStatResult session_fs_write_file_request: SessionFSWriteFileRequest session_log_level: SessionLogLevel - session_mode: Mode + session_mode: SessionMode sessions_fork_request: SessionsForkRequest sessions_fork_result: SessionsForkResult shell_exec_request: ShellExecRequest @@ -7512,7 +7691,6 @@ class RPC: skills_discover_request: SkillsDiscoverRequest skills_enable_request: SkillsEnableRequest skills_load_diagnostics: SkillsLoadDiagnostics - slash_command_agent_prompt_mode: Mode slash_command_agent_prompt_result: SlashCommandAgentPromptResult slash_command_completed_result: SlashCommandCompletedResult slash_command_info: SlashCommandInfo @@ -7522,16 +7700,13 @@ class RPC: slash_command_kind: SlashCommandKind slash_command_text_result: SlashCommandTextResult task_agent_info: TaskAgentInfo - task_agent_info_execution_mode: TaskInfoExecutionMode - task_agent_info_status: TaskInfoStatus + task_execution_mode: TaskExecutionMode task_info: TaskInfo task_list: TaskList tasks_cancel_request: TasksCancelRequest tasks_cancel_result: TasksCancelResult task_shell_info: TaskShellInfo task_shell_info_attachment_mode: TaskShellInfoAttachmentMode - task_shell_info_execution_mode: TaskInfoExecutionMode - task_shell_info_status: TaskInfoStatus tasks_promote_to_background_request: TasksPromoteToBackgroundRequest tasks_promote_to_background_result: TasksPromoteToBackgroundResult tasks_remove_request: TasksRemoveRequest @@ -7540,6 +7715,7 @@ class RPC: tasks_send_message_result: TasksSendMessageResult tasks_start_agent_request: TasksStartAgentRequest tasks_start_agent_result: TasksStartAgentResult + task_status: TaskStatus tool: Tool tool_list: ToolList tools_list_request: ToolsListRequest @@ -7604,9 +7780,9 @@ def from_dict(obj: Any) -> 'RPC': connect_remote_session_params = ConnectRemoteSessionParams.from_dict(obj.get("ConnectRemoteSessionParams")) connect_request = ConnectRequest.from_dict(obj.get("ConnectRequest")) connect_result = ConnectResult.from_dict(obj.get("ConnectResult")) + content_filter_mode = ContentFilterMode(obj.get("ContentFilterMode")) current_model = CurrentModel.from_dict(obj.get("CurrentModel")) discovered_mcp_server = DiscoveredMCPServer.from_dict(obj.get("DiscoveredMcpServer")) - discovered_mcp_server_source = MCPServerSource(obj.get("DiscoveredMcpServerSource")) discovered_mcp_server_type = DiscoveredMCPServerType(obj.get("DiscoveredMcpServerType")) extension = Extension.from_dict(obj.get("Extension")) extension_list = ExtensionList.from_dict(obj.get("ExtensionList")) @@ -7616,6 +7792,8 @@ def from_dict(obj: Any) -> 'RPC': extension_status = ExtensionStatus(obj.get("ExtensionStatus")) external_tool_result = from_union([ExternalToolTextResultForLlm.from_dict, from_str], obj.get("ExternalToolResult")) external_tool_text_result_for_llm = ExternalToolTextResultForLlm.from_dict(obj.get("ExternalToolTextResultForLlm")) + external_tool_text_result_for_llm_binary_results_for_llm = ExternalToolTextResultForLlmBinaryResultsForLlm.from_dict(obj.get("ExternalToolTextResultForLlmBinaryResultsForLlm")) + external_tool_text_result_for_llm_binary_results_for_llm_type = ExternalToolTextResultForLlmBinaryResultsForLlmType(obj.get("ExternalToolTextResultForLlmBinaryResultsForLlmType")) external_tool_text_result_for_llm_content = ExternalToolTextResultForLlmContent.from_dict(obj.get("ExternalToolTextResultForLlmContent")) external_tool_text_result_for_llm_content_audio = ExternalToolTextResultForLlmContentAudio.from_dict(obj.get("ExternalToolTextResultForLlmContentAudio")) external_tool_text_result_for_llm_content_image = ExternalToolTextResultForLlmContentImage.from_dict(obj.get("ExternalToolTextResultForLlmContentImage")) @@ -7626,9 +7804,7 @@ def from_dict(obj: Any) -> 'RPC': external_tool_text_result_for_llm_content_resource_link_icon_theme = ExternalToolTextResultForLlmContentResourceLinkIconTheme(obj.get("ExternalToolTextResultForLlmContentResourceLinkIconTheme")) external_tool_text_result_for_llm_content_terminal = ExternalToolTextResultForLlmContentTerminal.from_dict(obj.get("ExternalToolTextResultForLlmContentTerminal")) external_tool_text_result_for_llm_content_text = ExternalToolTextResultForLlmContentText.from_dict(obj.get("ExternalToolTextResultForLlmContentText")) - filter_mapping = from_union([lambda x: from_dict(FilterMappingString, x), FilterMappingString], obj.get("FilterMapping")) - filter_mapping_string = FilterMappingString(obj.get("FilterMappingString")) - filter_mapping_value = FilterMappingString(obj.get("FilterMappingValue")) + filter_mapping = from_union([lambda x: from_dict(ContentFilterMode, x), ContentFilterMode], obj.get("FilterMapping")) fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) handle_pending_tool_call_request = HandlePendingToolCallRequest.from_dict(obj.get("HandlePendingToolCallRequest")) @@ -7658,13 +7834,11 @@ def from_dict(obj: Any) -> 'RPC': 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_http_auth = MCPServerConfigHTTPAuth.from_dict(obj.get("McpServerConfigHttpAuth")) mcp_server_config_http_oauth_grant_type = MCPServerConfigHTTPOauthGrantType(obj.get("McpServerConfigHttpOauthGrantType")) mcp_server_config_http_type = MCPServerConfigHTTPType(obj.get("McpServerConfigHttpType")) - mcp_server_config_local = MCPServerConfigLocal.from_dict(obj.get("McpServerConfigLocal")) - mcp_server_config_local_type = MCPServerConfigLocalType(obj.get("McpServerConfigLocalType")) + mcp_server_config_stdio = MCPServerConfigStdio.from_dict(obj.get("McpServerConfigStdio")) mcp_server_list = MCPServerList.from_dict(obj.get("McpServerList")) - mcp_server_source = MCPServerSource(obj.get("McpServerSource")) - mcp_server_status = MCPServerStatus(obj.get("McpServerStatus")) model = Model.from_dict(obj.get("Model")) model_billing = ModelBilling.from_dict(obj.get("ModelBilling")) model_billing_token_prices = ModelBillingTokenPrices.from_dict(obj.get("ModelBillingTokenPrices")) @@ -7680,6 +7854,7 @@ def from_dict(obj: Any) -> 'RPC': model_picker_category = ModelPickerCategory(obj.get("ModelPickerCategory")) model_picker_price_category = ModelPickerPriceCategory(obj.get("ModelPickerPriceCategory")) model_policy = ModelPolicy.from_dict(obj.get("ModelPolicy")) + model_policy_state = ModelPolicyState(obj.get("ModelPolicyState")) models_list_request = ModelsListRequest.from_dict(obj.get("ModelsListRequest")) model_switch_to_request = ModelSwitchToRequest.from_dict(obj.get("ModelSwitchToRequest")) model_switch_to_result = ModelSwitchToResult.from_dict(obj.get("ModelSwitchToResult")) @@ -7751,14 +7926,20 @@ def from_dict(obj: Any) -> 'RPC': 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_capabilities = SessionFSSetProviderCapabilities.from_dict(obj.get("SessionFsSetProviderCapabilities")) session_fs_set_provider_conventions = SessionFSSetProviderConventions(obj.get("SessionFsSetProviderConventions")) session_fs_set_provider_request = SessionFSSetProviderRequest.from_dict(obj.get("SessionFsSetProviderRequest")) session_fs_set_provider_result = SessionFSSetProviderResult.from_dict(obj.get("SessionFsSetProviderResult")) + session_fs_sqlite_exists_request = SessionFSSqliteExistsRequest.from_dict(obj.get("SessionFsSqliteExistsRequest")) + session_fs_sqlite_exists_result = SessionFSSqliteExistsResult.from_dict(obj.get("SessionFsSqliteExistsResult")) + session_fs_sqlite_query_request = SessionFSSqliteQueryRequest.from_dict(obj.get("SessionFsSqliteQueryRequest")) + session_fs_sqlite_query_result = SessionFSSqliteQueryResult.from_dict(obj.get("SessionFsSqliteQueryResult")) + session_fs_sqlite_query_type = SessionFSSqliteQueryType(obj.get("SessionFsSqliteQueryType")) 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 = Mode(obj.get("SessionMode")) + 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")) @@ -7773,7 +7954,6 @@ def from_dict(obj: Any) -> 'RPC': skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest")) skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest")) skills_load_diagnostics = SkillsLoadDiagnostics.from_dict(obj.get("SkillsLoadDiagnostics")) - slash_command_agent_prompt_mode = Mode(obj.get("SlashCommandAgentPromptMode")) slash_command_agent_prompt_result = SlashCommandAgentPromptResult.from_dict(obj.get("SlashCommandAgentPromptResult")) slash_command_completed_result = SlashCommandCompletedResult.from_dict(obj.get("SlashCommandCompletedResult")) slash_command_info = SlashCommandInfo.from_dict(obj.get("SlashCommandInfo")) @@ -7783,16 +7963,13 @@ def from_dict(obj: Any) -> 'RPC': slash_command_kind = SlashCommandKind(obj.get("SlashCommandKind")) slash_command_text_result = SlashCommandTextResult.from_dict(obj.get("SlashCommandTextResult")) task_agent_info = TaskAgentInfo.from_dict(obj.get("TaskAgentInfo")) - task_agent_info_execution_mode = TaskInfoExecutionMode(obj.get("TaskAgentInfoExecutionMode")) - task_agent_info_status = TaskInfoStatus(obj.get("TaskAgentInfoStatus")) + task_execution_mode = TaskExecutionMode(obj.get("TaskExecutionMode")) task_info = TaskInfo.from_dict(obj.get("TaskInfo")) task_list = TaskList.from_dict(obj.get("TaskList")) tasks_cancel_request = TasksCancelRequest.from_dict(obj.get("TasksCancelRequest")) tasks_cancel_result = TasksCancelResult.from_dict(obj.get("TasksCancelResult")) task_shell_info = TaskShellInfo.from_dict(obj.get("TaskShellInfo")) task_shell_info_attachment_mode = TaskShellInfoAttachmentMode(obj.get("TaskShellInfoAttachmentMode")) - task_shell_info_execution_mode = TaskInfoExecutionMode(obj.get("TaskShellInfoExecutionMode")) - task_shell_info_status = TaskInfoStatus(obj.get("TaskShellInfoStatus")) tasks_promote_to_background_request = TasksPromoteToBackgroundRequest.from_dict(obj.get("TasksPromoteToBackgroundRequest")) tasks_promote_to_background_result = TasksPromoteToBackgroundResult.from_dict(obj.get("TasksPromoteToBackgroundResult")) tasks_remove_request = TasksRemoveRequest.from_dict(obj.get("TasksRemoveRequest")) @@ -7801,6 +7978,7 @@ def from_dict(obj: Any) -> 'RPC': tasks_send_message_result = TasksSendMessageResult.from_dict(obj.get("TasksSendMessageResult")) tasks_start_agent_request = TasksStartAgentRequest.from_dict(obj.get("TasksStartAgentRequest")) tasks_start_agent_result = TasksStartAgentResult.from_dict(obj.get("TasksStartAgentResult")) + task_status = TaskStatus(obj.get("TaskStatus")) tool = Tool.from_dict(obj.get("Tool")) tool_list = ToolList.from_dict(obj.get("ToolList")) tools_list_request = ToolsListRequest.from_dict(obj.get("ToolsListRequest")) @@ -7838,7 +8016,7 @@ def from_dict(obj: Any) -> 'RPC': 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_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_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_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_billing_token_prices, 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_picker_category, model_picker_price_category, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, remote_enable_request, remote_enable_result, remote_session_connection_result, remote_session_mode, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, 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_entry_type, 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_conventions, 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, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_load_diagnostics, slash_command_agent_prompt_mode, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_text_result, task_agent_info, task_agent_info_execution_mode, task_agent_info_status, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, task_shell_info_execution_mode, task_shell_info_status, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_list, 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, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, 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_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result) + return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connected_remote_session_metadata, connected_remote_session_metadata_kind, connected_remote_session_metadata_repository, connect_remote_session_params, connect_request, connect_result, content_filter_mode, current_model, discovered_mcp_server, discovered_mcp_server_type, extension, extension_list, extensions_disable_request, extensions_enable_request, extension_source, extension_status, external_tool_result, external_tool_text_result_for_llm, external_tool_text_result_for_llm_binary_results_for_llm, external_tool_text_result_for_llm_binary_results_for_llm_type, external_tool_text_result_for_llm_content, external_tool_text_result_for_llm_content_audio, external_tool_text_result_for_llm_content_image, external_tool_text_result_for_llm_content_resource, external_tool_text_result_for_llm_content_resource_details, external_tool_text_result_for_llm_content_resource_link, external_tool_text_result_for_llm_content_resource_link_icon, external_tool_text_result_for_llm_content_resource_link_icon_theme, external_tool_text_result_for_llm_content_terminal, external_tool_text_result_for_llm_content_text, filter_mapping, fleet_start_request, fleet_start_result, handle_pending_tool_call_request, handle_pending_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, instructions_sources_location, instructions_sources_type, log_request, log_result, mcp_config_add_request, mcp_config_disable_request, mcp_config_enable_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_oauth_login_request, mcp_oauth_login_result, mcp_server, mcp_server_config, mcp_server_config_http, mcp_server_config_http_auth, mcp_server_config_http_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_stdio, mcp_server_list, model, model_billing, model_billing_token_prices, 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_picker_category, model_picker_price_category, model_policy, model_policy_state, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, remote_enable_request, remote_enable_result, remote_session_connection_result, remote_session_mode, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, 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_entry_type, 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_capabilities, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_sqlite_exists_request, session_fs_sqlite_exists_result, session_fs_sqlite_query_request, session_fs_sqlite_query_result, session_fs_sqlite_query_type, 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, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_load_diagnostics, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_text_result, task_agent_info, task_execution_mode, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, task_status, tool, tool_list, 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, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, 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_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, 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 = {} @@ -7865,9 +8043,9 @@ def to_dict(self) -> dict: result["ConnectRemoteSessionParams"] = to_class(ConnectRemoteSessionParams, self.connect_remote_session_params) result["ConnectRequest"] = to_class(ConnectRequest, self.connect_request) result["ConnectResult"] = to_class(ConnectResult, self.connect_result) + result["ContentFilterMode"] = to_enum(ContentFilterMode, self.content_filter_mode) result["CurrentModel"] = to_class(CurrentModel, self.current_model) result["DiscoveredMcpServer"] = to_class(DiscoveredMCPServer, self.discovered_mcp_server) - result["DiscoveredMcpServerSource"] = to_enum(MCPServerSource, self.discovered_mcp_server_source) result["DiscoveredMcpServerType"] = to_enum(DiscoveredMCPServerType, self.discovered_mcp_server_type) result["Extension"] = to_class(Extension, self.extension) result["ExtensionList"] = to_class(ExtensionList, self.extension_list) @@ -7877,6 +8055,8 @@ def to_dict(self) -> dict: result["ExtensionStatus"] = to_enum(ExtensionStatus, self.extension_status) result["ExternalToolResult"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str], self.external_tool_result) result["ExternalToolTextResultForLlm"] = to_class(ExternalToolTextResultForLlm, self.external_tool_text_result_for_llm) + result["ExternalToolTextResultForLlmBinaryResultsForLlm"] = to_class(ExternalToolTextResultForLlmBinaryResultsForLlm, self.external_tool_text_result_for_llm_binary_results_for_llm) + result["ExternalToolTextResultForLlmBinaryResultsForLlmType"] = to_enum(ExternalToolTextResultForLlmBinaryResultsForLlmType, self.external_tool_text_result_for_llm_binary_results_for_llm_type) result["ExternalToolTextResultForLlmContent"] = to_class(ExternalToolTextResultForLlmContent, self.external_tool_text_result_for_llm_content) result["ExternalToolTextResultForLlmContentAudio"] = to_class(ExternalToolTextResultForLlmContentAudio, self.external_tool_text_result_for_llm_content_audio) result["ExternalToolTextResultForLlmContentImage"] = to_class(ExternalToolTextResultForLlmContentImage, self.external_tool_text_result_for_llm_content_image) @@ -7887,9 +8067,7 @@ def to_dict(self) -> dict: result["ExternalToolTextResultForLlmContentResourceLinkIconTheme"] = to_enum(ExternalToolTextResultForLlmContentResourceLinkIconTheme, self.external_tool_text_result_for_llm_content_resource_link_icon_theme) result["ExternalToolTextResultForLlmContentTerminal"] = to_class(ExternalToolTextResultForLlmContentTerminal, self.external_tool_text_result_for_llm_content_terminal) result["ExternalToolTextResultForLlmContentText"] = to_class(ExternalToolTextResultForLlmContentText, self.external_tool_text_result_for_llm_content_text) - 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["FilterMappingString"] = to_enum(FilterMappingString, self.filter_mapping_string) - result["FilterMappingValue"] = to_enum(FilterMappingString, self.filter_mapping_value) + result["FilterMapping"] = from_union([lambda x: from_dict(lambda x: to_enum(ContentFilterMode, x), x), lambda x: to_enum(ContentFilterMode, x)], self.filter_mapping) result["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) result["HandlePendingToolCallRequest"] = to_class(HandlePendingToolCallRequest, self.handle_pending_tool_call_request) @@ -7919,13 +8097,11 @@ def to_dict(self) -> dict: 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["McpServerConfigHttpAuth"] = to_class(MCPServerConfigHTTPAuth, self.mcp_server_config_http_auth) result["McpServerConfigHttpOauthGrantType"] = to_enum(MCPServerConfigHTTPOauthGrantType, self.mcp_server_config_http_oauth_grant_type) result["McpServerConfigHttpType"] = to_enum(MCPServerConfigHTTPType, self.mcp_server_config_http_type) - result["McpServerConfigLocal"] = to_class(MCPServerConfigLocal, self.mcp_server_config_local) - result["McpServerConfigLocalType"] = to_enum(MCPServerConfigLocalType, self.mcp_server_config_local_type) + result["McpServerConfigStdio"] = to_class(MCPServerConfigStdio, self.mcp_server_config_stdio) result["McpServerList"] = to_class(MCPServerList, self.mcp_server_list) - result["McpServerSource"] = to_enum(MCPServerSource, self.mcp_server_source) - result["McpServerStatus"] = to_enum(MCPServerStatus, self.mcp_server_status) result["Model"] = to_class(Model, self.model) result["ModelBilling"] = to_class(ModelBilling, self.model_billing) result["ModelBillingTokenPrices"] = to_class(ModelBillingTokenPrices, self.model_billing_token_prices) @@ -7941,6 +8117,7 @@ def to_dict(self) -> dict: result["ModelPickerCategory"] = to_enum(ModelPickerCategory, self.model_picker_category) result["ModelPickerPriceCategory"] = to_enum(ModelPickerPriceCategory, self.model_picker_price_category) result["ModelPolicy"] = to_class(ModelPolicy, self.model_policy) + result["ModelPolicyState"] = to_enum(ModelPolicyState, self.model_policy_state) result["ModelsListRequest"] = to_class(ModelsListRequest, self.models_list_request) result["ModelSwitchToRequest"] = to_class(ModelSwitchToRequest, self.model_switch_to_request) result["ModelSwitchToResult"] = to_class(ModelSwitchToResult, self.model_switch_to_result) @@ -8012,14 +8189,20 @@ def to_dict(self) -> dict: 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["SessionFsSetProviderCapabilities"] = to_class(SessionFSSetProviderCapabilities, self.session_fs_set_provider_capabilities) result["SessionFsSetProviderConventions"] = to_enum(SessionFSSetProviderConventions, self.session_fs_set_provider_conventions) result["SessionFsSetProviderRequest"] = to_class(SessionFSSetProviderRequest, self.session_fs_set_provider_request) result["SessionFsSetProviderResult"] = to_class(SessionFSSetProviderResult, self.session_fs_set_provider_result) + result["SessionFsSqliteExistsRequest"] = to_class(SessionFSSqliteExistsRequest, self.session_fs_sqlite_exists_request) + result["SessionFsSqliteExistsResult"] = to_class(SessionFSSqliteExistsResult, self.session_fs_sqlite_exists_result) + result["SessionFsSqliteQueryRequest"] = to_class(SessionFSSqliteQueryRequest, self.session_fs_sqlite_query_request) + result["SessionFsSqliteQueryResult"] = to_class(SessionFSSqliteQueryResult, self.session_fs_sqlite_query_result) + result["SessionFsSqliteQueryType"] = to_enum(SessionFSSqliteQueryType, self.session_fs_sqlite_query_type) 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(Mode, self.session_mode) + 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) @@ -8034,7 +8217,6 @@ def to_dict(self) -> dict: result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request) result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request) result["SkillsLoadDiagnostics"] = to_class(SkillsLoadDiagnostics, self.skills_load_diagnostics) - result["SlashCommandAgentPromptMode"] = to_enum(Mode, self.slash_command_agent_prompt_mode) result["SlashCommandAgentPromptResult"] = to_class(SlashCommandAgentPromptResult, self.slash_command_agent_prompt_result) result["SlashCommandCompletedResult"] = to_class(SlashCommandCompletedResult, self.slash_command_completed_result) result["SlashCommandInfo"] = to_class(SlashCommandInfo, self.slash_command_info) @@ -8044,16 +8226,13 @@ def to_dict(self) -> dict: result["SlashCommandKind"] = to_enum(SlashCommandKind, self.slash_command_kind) result["SlashCommandTextResult"] = to_class(SlashCommandTextResult, self.slash_command_text_result) result["TaskAgentInfo"] = to_class(TaskAgentInfo, self.task_agent_info) - result["TaskAgentInfoExecutionMode"] = to_enum(TaskInfoExecutionMode, self.task_agent_info_execution_mode) - result["TaskAgentInfoStatus"] = to_enum(TaskInfoStatus, self.task_agent_info_status) + result["TaskExecutionMode"] = to_enum(TaskExecutionMode, self.task_execution_mode) result["TaskInfo"] = to_class(TaskInfo, self.task_info) result["TaskList"] = to_class(TaskList, self.task_list) result["TasksCancelRequest"] = to_class(TasksCancelRequest, self.tasks_cancel_request) result["TasksCancelResult"] = to_class(TasksCancelResult, self.tasks_cancel_result) result["TaskShellInfo"] = to_class(TaskShellInfo, self.task_shell_info) result["TaskShellInfoAttachmentMode"] = to_enum(TaskShellInfoAttachmentMode, self.task_shell_info_attachment_mode) - result["TaskShellInfoExecutionMode"] = to_enum(TaskInfoExecutionMode, self.task_shell_info_execution_mode) - result["TaskShellInfoStatus"] = to_enum(TaskInfoStatus, self.task_shell_info_status) result["TasksPromoteToBackgroundRequest"] = to_class(TasksPromoteToBackgroundRequest, self.tasks_promote_to_background_request) result["TasksPromoteToBackgroundResult"] = to_class(TasksPromoteToBackgroundResult, self.tasks_promote_to_background_result) result["TasksRemoveRequest"] = to_class(TasksRemoveRequest, self.tasks_remove_request) @@ -8062,6 +8241,7 @@ def to_dict(self) -> dict: result["TasksSendMessageResult"] = to_class(TasksSendMessageResult, self.tasks_send_message_result) result["TasksStartAgentRequest"] = to_class(TasksStartAgentRequest, self.tasks_start_agent_request) result["TasksStartAgentResult"] = to_class(TasksStartAgentResult, self.tasks_start_agent_result) + result["TaskStatus"] = to_enum(TaskStatus, self.task_status) result["Tool"] = to_class(Tool, self.tool) result["ToolList"] = to_class(ToolList, self.tool_list) result["ToolsListRequest"] = to_class(ToolsListRequest, self.tools_list_request) @@ -8108,16 +8288,10 @@ def rpc_to_dict(x: RPC) -> Any: return to_class(RPC, x) -DiscoveredMcpServerSource = MCPServerSource ExternalToolResult = ExternalToolTextResultForLlm FilterMapping = dict -FilterMappingValue = FilterMappingString -SessionMode = Mode -SlashCommandAgentPromptMode = Mode -TaskAgentInfoExecutionMode = TaskInfoExecutionMode -TaskAgentInfoStatus = TaskInfoStatus -TaskShellInfoExecutionMode = TaskInfoExecutionMode -TaskShellInfoStatus = TaskInfoStatus +TaskInfoExecutionMode = TaskExecutionMode +TaskInfoStatus = TaskStatus def _timeout_kwargs(timeout: float | None) -> dict: """Build keyword arguments for optional timeout forwarding.""" @@ -8329,9 +8503,9 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def get(self, *, timeout: float | None = None) -> Mode: - "Gets the current agent interaction mode.\n\nReturns:\n The agent mode. Valid values: \"interactive\", \"plan\", \"autopilot\"." - return Mode(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) + async def get(self, *, timeout: float | None = None) -> SessionMode: + "Gets the current agent interaction mode.\n\nReturns:\n The session mode the agent is operating in" + return SessionMode(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))) async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> None: "Sets the current agent interaction mode.\n\nArgs:\n params: Agent interaction mode to apply to the session." @@ -8816,6 +8990,12 @@ async def rm(self, params: SessionFSRmRequest) -> SessionFSError | None: async def rename(self, params: SessionFSRenameRequest) -> SessionFSError | None: "Renames or moves a path in the client-provided session filesystem.\n\nArgs:\n params: Source and destination paths for renaming or moving an entry in the client-provided session filesystem.\n\nReturns:\n Describes a filesystem error." pass + async def sqlite_query(self, params: SessionFSSqliteQueryRequest) -> SessionFSSqliteQueryResult: + "Executes a SQLite query against the per-session database.\n\nArgs:\n params: SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database.\n\nReturns:\n Query results including rows, columns, and rows affected, or a filesystem error if execution failed." + pass + async def sqlite_exists(self, params: SessionFSSqliteExistsRequest) -> SessionFSSqliteExistsResult: + "Checks whether the per-session SQLite database already exists, without creating it.\n\nArgs:\n params: Identifies the target session.\n\nReturns:\n Indicates whether the per-session SQLite database already exists." + pass @dataclass class ClientSessionApiHandlers: @@ -8896,3 +9076,17 @@ async def handle_session_fs_rename(params: dict) -> dict | None: result = await handler.rename(request) return result.to_dict() if result is not None else None client.set_request_handler("sessionFs.rename", handle_session_fs_rename) + async def handle_session_fs_sqlite_query(params: dict) -> dict | None: + request = SessionFSSqliteQueryRequest.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.sqlite_query(request) + return result.to_dict() + client.set_request_handler("sessionFs.sqliteQuery", handle_session_fs_sqlite_query) + async def handle_session_fs_sqlite_exists(params: dict) -> dict | None: + request = SessionFSSqliteExistsRequest.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.sqlite_exists(request) + return result.to_dict() + client.set_request_handler("sessionFs.sqliteExists", handle_session_fs_sqlite_exists) diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 696d5a0e5..a540dda0a 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -7,7 +7,7 @@ from collections.abc import Callable from dataclasses import dataclass -from datetime import datetime +from datetime import datetime, timedelta from enum import Enum from typing import Any, TypeVar, cast from uuid import UUID @@ -43,6 +43,23 @@ def to_float(x: Any) -> float: return float(x) +def from_timedelta(x: Any) -> timedelta: + assert isinstance(x, (float, int)) and not isinstance(x, bool) + return timedelta(milliseconds=float(x)) + + +def to_timedelta_int(x: timedelta) -> int: + assert isinstance(x, timedelta) + milliseconds = x.total_seconds() * 1000.0 + assert milliseconds.is_integer() + return int(milliseconds) + + +def to_timedelta(x: timedelta) -> float: + assert isinstance(x, timedelta) + return x.total_seconds() * 1000.0 + + def from_bool(x: Any) -> bool: assert isinstance(x, bool) return x @@ -228,6 +245,8 @@ def _compat_to_json_value(value: Any) -> Any: return value.value if isinstance(value, datetime): return value.isoformat() + if isinstance(value, timedelta): + return value.total_seconds() * 1000.0 if isinstance(value, UUID): return str(value) if isinstance(value, list): @@ -663,10 +682,10 @@ class AssistantUsageData: cache_write_tokens: float | None = None copilot_usage: AssistantUsageCopilotUsage | None = None cost: float | None = None - duration: float | None = None + duration: timedelta | None = None initiator: str | None = None input_tokens: float | None = None - inter_token_latency_ms: float | None = None + inter_token_latency_ms: timedelta | None = None output_tokens: float | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None @@ -674,7 +693,7 @@ class AssistantUsageData: quota_snapshots: dict[str, AssistantUsageQuotaSnapshot] | None = None reasoning_effort: str | None = None reasoning_tokens: float | None = None - ttft_ms: float | None = None + ttft_ms: timedelta | None = None @staticmethod def from_dict(obj: Any) -> "AssistantUsageData": @@ -686,17 +705,17 @@ def from_dict(obj: Any) -> "AssistantUsageData": cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens")) copilot_usage = from_union([from_none, AssistantUsageCopilotUsage.from_dict], obj.get("copilotUsage")) cost = from_union([from_none, from_float], obj.get("cost")) - duration = from_union([from_none, from_float], obj.get("duration")) + duration = from_union([from_none, from_timedelta], obj.get("duration")) initiator = from_union([from_none, from_str], obj.get("initiator")) input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) - inter_token_latency_ms = from_union([from_none, from_float], obj.get("interTokenLatencyMs")) + inter_token_latency_ms = from_union([from_none, from_timedelta], obj.get("interTokenLatencyMs")) output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) provider_call_id = from_union([from_none, from_str], obj.get("providerCallId")) quota_snapshots = from_union([from_none, lambda x: from_dict(AssistantUsageQuotaSnapshot.from_dict, x)], obj.get("quotaSnapshots")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) reasoning_tokens = from_union([from_none, from_float], obj.get("reasoningTokens")) - ttft_ms = from_union([from_none, from_float], obj.get("ttftMs")) + ttft_ms = from_union([from_none, from_timedelta], obj.get("ttftMs")) return AssistantUsageData( model=model, api_call_id=api_call_id, @@ -734,13 +753,13 @@ def to_dict(self) -> dict: if self.cost is not None: result["cost"] = from_union([from_none, to_float], self.cost) if self.duration is not None: - result["duration"] = from_union([from_none, to_float], self.duration) + result["duration"] = from_union([from_none, to_timedelta], self.duration) if self.initiator is not None: result["initiator"] = from_union([from_none, from_str], self.initiator) if self.input_tokens is not None: result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) if self.inter_token_latency_ms is not None: - result["interTokenLatencyMs"] = from_union([from_none, to_float], self.inter_token_latency_ms) + result["interTokenLatencyMs"] = from_union([from_none, to_timedelta], self.inter_token_latency_ms) if self.output_tokens is not None: result["outputTokens"] = from_union([from_none, to_float], self.output_tokens) if self.parent_tool_call_id is not None: @@ -754,7 +773,7 @@ def to_dict(self) -> dict: if self.reasoning_tokens is not None: result["reasoningTokens"] = from_union([from_none, to_float], self.reasoning_tokens) if self.ttft_ms is not None: - result["ttftMs"] = from_union([from_none, to_float], self.ttft_ms) + result["ttftMs"] = from_union([from_none, to_timedelta], self.ttft_ms) return result @@ -810,13 +829,13 @@ def to_dict(self) -> dict: class AutoModeSwitchCompletedData: "Auto mode switch completion notification" request_id: str - response: str + response: AutoModeSwitchResponse @staticmethod def from_dict(obj: Any) -> "AutoModeSwitchCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - response = from_str(obj.get("response")) + response = parse_enum(AutoModeSwitchResponse, obj.get("response")) return AutoModeSwitchCompletedData( request_id=request_id, response=response, @@ -825,7 +844,7 @@ def from_dict(obj: Any) -> "AutoModeSwitchCompletedData": def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - result["response"] = from_str(self.response) + result["response"] = to_enum(AutoModeSwitchResponse, self.response) return result @@ -1020,7 +1039,7 @@ class CompactionCompleteCompactionTokensUsed: cache_read_tokens: float | None = None cache_write_tokens: float | None = None copilot_usage: CompactionCompleteCompactionTokensUsedCopilotUsage | None = None - duration: float | None = None + duration: timedelta | None = None input_tokens: float | None = None model: str | None = None output_tokens: float | None = None @@ -1031,7 +1050,7 @@ def from_dict(obj: Any) -> "CompactionCompleteCompactionTokensUsed": cache_read_tokens = from_union([from_none, from_float], obj.get("cacheReadTokens")) cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens")) copilot_usage = from_union([from_none, CompactionCompleteCompactionTokensUsedCopilotUsage.from_dict], obj.get("copilotUsage")) - duration = from_union([from_none, from_float], obj.get("duration")) + duration = from_union([from_none, from_timedelta], obj.get("duration")) input_tokens = from_union([from_none, from_float], obj.get("inputTokens")) model = from_union([from_none, from_str], obj.get("model")) output_tokens = from_union([from_none, from_float], obj.get("outputTokens")) @@ -1054,7 +1073,7 @@ def to_dict(self) -> dict: if self.copilot_usage is not None: result["copilotUsage"] = from_union([from_none, lambda x: to_class(CompactionCompleteCompactionTokensUsedCopilotUsage, x)], self.copilot_usage) if self.duration is not None: - result["duration"] = from_union([from_none, to_float], self.duration) + result["duration"] = from_union([from_none, to_timedelta], self.duration) if self.input_tokens is not None: result["inputTokens"] = from_union([from_none, to_float], self.input_tokens) if self.model is not None: @@ -1334,7 +1353,7 @@ class ExitPlanModeCompletedData: approved: bool | None = None auto_approve_edits: bool | None = None feedback: str | None = None - selected_action: str | None = None + selected_action: ExitPlanModeAction | None = None @staticmethod def from_dict(obj: Any) -> "ExitPlanModeCompletedData": @@ -1343,7 +1362,7 @@ def from_dict(obj: Any) -> "ExitPlanModeCompletedData": approved = from_union([from_none, from_bool], obj.get("approved")) auto_approve_edits = from_union([from_none, from_bool], obj.get("autoApproveEdits")) feedback = from_union([from_none, from_str], obj.get("feedback")) - selected_action = from_union([from_none, from_str], obj.get("selectedAction")) + selected_action = from_union([from_none, lambda x: parse_enum(ExitPlanModeAction, x)], obj.get("selectedAction")) return ExitPlanModeCompletedData( request_id=request_id, approved=approved, @@ -1362,25 +1381,25 @@ def to_dict(self) -> dict: if self.feedback is not None: result["feedback"] = from_union([from_none, from_str], self.feedback) if self.selected_action is not None: - result["selectedAction"] = from_union([from_none, from_str], self.selected_action) + result["selectedAction"] = from_union([from_none, lambda x: to_enum(ExitPlanModeAction, x)], self.selected_action) return result @dataclass class ExitPlanModeRequestedData: "Plan approval request with plan content and available user actions" - actions: list[str] + actions: list[ExitPlanModeAction] plan_content: str - recommended_action: str + recommended_action: ExitPlanModeAction request_id: str summary: str @staticmethod def from_dict(obj: Any) -> "ExitPlanModeRequestedData": assert isinstance(obj, dict) - actions = from_list(from_str, obj.get("actions")) + actions = from_list(lambda x: parse_enum(ExitPlanModeAction, x), obj.get("actions")) plan_content = from_str(obj.get("planContent")) - recommended_action = from_str(obj.get("recommendedAction")) + recommended_action = parse_enum(ExitPlanModeAction, obj.get("recommendedAction")) request_id = from_str(obj.get("requestId")) summary = from_str(obj.get("summary")) return ExitPlanModeRequestedData( @@ -1393,9 +1412,9 @@ def from_dict(obj: Any) -> "ExitPlanModeRequestedData": def to_dict(self) -> dict: result: dict = {} - result["actions"] = from_list(from_str, self.actions) + result["actions"] = from_list(lambda x: to_enum(ExitPlanModeAction, x), self.actions) result["planContent"] = from_str(self.plan_content) - result["recommendedAction"] = from_str(self.recommended_action) + result["recommendedAction"] = to_enum(ExitPlanModeAction, self.recommended_action) result["requestId"] = from_str(self.request_id) result["summary"] = from_str(self.summary) return result @@ -1698,17 +1717,17 @@ def to_dict(self) -> dict: class McpServersLoadedServer: "Schema for the `McpServersLoadedServer` type." name: str - status: McpServersLoadedServerStatus + status: McpServerStatus error: str | None = None - source: str | None = None + source: McpServerSource | None = None @staticmethod def from_dict(obj: Any) -> "McpServersLoadedServer": assert isinstance(obj, dict) name = from_str(obj.get("name")) - status = parse_enum(McpServersLoadedServerStatus, obj.get("status")) + status = parse_enum(McpServerStatus, obj.get("status")) error = from_union([from_none, from_str], obj.get("error")) - source = from_union([from_none, from_str], obj.get("source")) + source = from_union([from_none, lambda x: parse_enum(McpServerSource, x)], obj.get("source")) return McpServersLoadedServer( name=name, status=status, @@ -1719,11 +1738,11 @@ def from_dict(obj: Any) -> "McpServersLoadedServer": def to_dict(self) -> dict: result: dict = {} result["name"] = from_str(self.name) - result["status"] = to_enum(McpServersLoadedServerStatus, self.status) + result["status"] = to_enum(McpServerStatus, self.status) if self.error is not None: result["error"] = from_union([from_none, from_str], self.error) if self.source is not None: - result["source"] = from_union([from_none, from_str], self.source) + result["source"] = from_union([from_none, lambda x: to_enum(McpServerSource, x)], self.source) return result @@ -1732,7 +1751,7 @@ class ModelCallFailureData: "Failed LLM API call metadata for telemetry" source: ModelCallFailureSource api_call_id: str | None = None - duration_ms: float | None = None + duration_ms: timedelta | None = None error_message: str | None = None initiator: str | None = None model: str | None = None @@ -1744,7 +1763,7 @@ def from_dict(obj: Any) -> "ModelCallFailureData": assert isinstance(obj, dict) source = parse_enum(ModelCallFailureSource, obj.get("source")) api_call_id = from_union([from_none, from_str], obj.get("apiCallId")) - duration_ms = from_union([from_none, from_float], obj.get("durationMs")) + duration_ms = from_union([from_none, from_timedelta], obj.get("durationMs")) error_message = from_union([from_none, from_str], obj.get("errorMessage")) initiator = from_union([from_none, from_str], obj.get("initiator")) model = from_union([from_none, from_str], obj.get("model")) @@ -1767,7 +1786,7 @@ def to_dict(self) -> dict: if self.api_call_id is not None: result["apiCallId"] = from_union([from_none, from_str], self.api_call_id) if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + result["durationMs"] = from_union([from_none, to_timedelta], self.duration_ms) if self.error_message is not None: result["errorMessage"] = from_union([from_none, from_str], self.error_message) if self.initiator is not None: @@ -1826,14 +1845,14 @@ class PermissionPromptRequest: "Derived user-facing permission prompt details for UI consumers" kind: PermissionPromptRequestKind access_kind: PermissionPromptRequestPathAccessKind | None = None - action: PermissionPromptRequestMemoryAction | None = None + action: PermissionRequestMemoryAction | None = None args: Any | None = None can_offer_session_approval: bool | None = None capabilities: list[str] | None = None citations: str | None = None command_identifiers: list[str] | None = None diff: str | None = None - direction: PermissionPromptRequestMemoryDirection | None = None + direction: PermissionRequestMemoryDirection | None = None extension_name: str | None = None fact: str | None = None file_name: str | None = None @@ -1860,14 +1879,14 @@ def from_dict(obj: Any) -> "PermissionPromptRequest": assert isinstance(obj, dict) kind = parse_enum(PermissionPromptRequestKind, obj.get("kind")) access_kind = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestPathAccessKind, x)], obj.get("accessKind")) - action = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestMemoryAction, x)], obj.get("action", "store")) + action = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryAction, x)], obj.get("action", "store")) args = from_union([from_none, lambda x: x], obj.get("args")) can_offer_session_approval = from_union([from_none, from_bool], obj.get("canOfferSessionApproval")) capabilities = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("capabilities")) citations = from_union([from_none, from_str], obj.get("citations")) command_identifiers = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("commandIdentifiers")) diff = from_union([from_none, from_str], obj.get("diff")) - direction = from_union([from_none, lambda x: parse_enum(PermissionPromptRequestMemoryDirection, x)], obj.get("direction")) + direction = from_union([from_none, lambda x: parse_enum(PermissionRequestMemoryDirection, x)], obj.get("direction")) extension_name = from_union([from_none, from_str], obj.get("extensionName")) fact = from_union([from_none, from_str], obj.get("fact")) file_name = from_union([from_none, from_str], obj.get("fileName")) @@ -1927,7 +1946,7 @@ def to_dict(self) -> dict: if self.access_kind is not None: result["accessKind"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestPathAccessKind, x)], self.access_kind) if self.action is not None: - result["action"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestMemoryAction, x)], self.action) + result["action"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryAction, x)], self.action) if self.args is not None: result["args"] = from_union([from_none, lambda x: x], self.args) if self.can_offer_session_approval is not None: @@ -1941,7 +1960,7 @@ def to_dict(self) -> dict: if self.diff is not None: result["diff"] = from_union([from_none, from_str], self.diff) if self.direction is not None: - result["direction"] = from_union([from_none, lambda x: to_enum(PermissionPromptRequestMemoryDirection, x)], self.direction) + result["direction"] = from_union([from_none, lambda x: to_enum(PermissionRequestMemoryDirection, x)], self.direction) if self.extension_name is not None: result["extensionName"] = from_union([from_none, from_str], self.extension_name) if self.fact is not None: @@ -2793,13 +2812,13 @@ def to_dict(self) -> dict: class SessionMcpServerStatusChangedData: "Schema for the `McpServerStatusChangedData` type." server_name: str - status: McpServerStatusChangedStatus + status: McpServerStatus @staticmethod def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": assert isinstance(obj, dict) server_name = from_str(obj.get("serverName")) - status = parse_enum(McpServerStatusChangedStatus, obj.get("status")) + status = parse_enum(McpServerStatus, obj.get("status")) return SessionMcpServerStatusChangedData( server_name=server_name, status=status, @@ -2808,7 +2827,7 @@ def from_dict(obj: Any) -> "SessionMcpServerStatusChangedData": def to_dict(self) -> dict: result: dict = {} result["serverName"] = from_str(self.server_name) - result["status"] = to_enum(McpServerStatusChangedStatus, self.status) + result["status"] = to_enum(McpServerStatus, self.status) return result @@ -2834,14 +2853,14 @@ def to_dict(self) -> dict: @dataclass class SessionModeChangedData: "Agent mode change details including previous and new modes" - new_mode: str - previous_mode: str + new_mode: SessionMode + previous_mode: SessionMode @staticmethod def from_dict(obj: Any) -> "SessionModeChangedData": assert isinstance(obj, dict) - new_mode = from_str(obj.get("newMode")) - previous_mode = from_str(obj.get("previousMode")) + new_mode = parse_enum(SessionMode, obj.get("newMode")) + previous_mode = parse_enum(SessionMode, obj.get("previousMode")) return SessionModeChangedData( new_mode=new_mode, previous_mode=previous_mode, @@ -2849,8 +2868,8 @@ def from_dict(obj: Any) -> "SessionModeChangedData": def to_dict(self) -> dict: result: dict = {} - result["newMode"] = from_str(self.new_mode) - result["previousMode"] = from_str(self.previous_mode) + result["newMode"] = to_enum(SessionMode, self.new_mode) + result["previousMode"] = to_enum(SessionMode, self.previous_mode) return result @@ -3027,7 +3046,7 @@ def to_dict(self) -> dict: class SessionScheduleCreatedData: "Scheduled prompt registered via /every or /after" id: int - interval_ms: int + interval_ms: timedelta prompt: str display_prompt: str | None = None recurring: bool | None = None @@ -3036,7 +3055,7 @@ class SessionScheduleCreatedData: def from_dict(obj: Any) -> "SessionScheduleCreatedData": assert isinstance(obj, dict) id = from_int(obj.get("id")) - interval_ms = from_int(obj.get("intervalMs")) + interval_ms = from_timedelta(obj.get("intervalMs")) prompt = from_str(obj.get("prompt")) display_prompt = from_union([from_none, from_str], obj.get("displayPrompt")) recurring = from_union([from_none, from_bool], obj.get("recurring")) @@ -3051,7 +3070,7 @@ def from_dict(obj: Any) -> "SessionScheduleCreatedData": def to_dict(self) -> dict: result: dict = {} result["id"] = to_int(self.id) - result["intervalMs"] = to_int(self.interval_ms) + result["intervalMs"] = to_timedelta_int(self.interval_ms) result["prompt"] = from_str(self.prompt) if self.display_prompt is not None: result["displayPrompt"] = from_union([from_none, from_str], self.display_prompt) @@ -3067,7 +3086,7 @@ class SessionShutdownData: model_metrics: dict[str, ShutdownModelMetric] session_start_time: float shutdown_type: ShutdownType - total_api_duration_ms: float + total_api_duration_ms: timedelta total_premium_requests: float conversation_tokens: float | None = None current_model: str | None = None @@ -3085,7 +3104,7 @@ def from_dict(obj: Any) -> "SessionShutdownData": model_metrics = from_dict(ShutdownModelMetric.from_dict, obj.get("modelMetrics")) session_start_time = from_float(obj.get("sessionStartTime")) shutdown_type = parse_enum(ShutdownType, obj.get("shutdownType")) - total_api_duration_ms = from_float(obj.get("totalApiDurationMs")) + total_api_duration_ms = from_timedelta(obj.get("totalApiDurationMs")) total_premium_requests = from_float(obj.get("totalPremiumRequests")) conversation_tokens = from_union([from_none, from_float], obj.get("conversationTokens")) current_model = from_union([from_none, from_str], obj.get("currentModel")) @@ -3118,7 +3137,7 @@ def to_dict(self) -> dict: result["modelMetrics"] = from_dict(lambda x: to_class(ShutdownModelMetric, x), self.model_metrics) result["sessionStartTime"] = to_float(self.session_start_time) result["shutdownType"] = to_enum(ShutdownType, self.shutdown_type) - result["totalApiDurationMs"] = to_float(self.total_api_duration_ms) + result["totalApiDurationMs"] = to_timedelta(self.total_api_duration_ms) result["totalPremiumRequests"] = to_float(self.total_premium_requests) if self.conversation_tokens is not None: result["conversationTokens"] = from_union([from_none, to_float], self.conversation_tokens) @@ -3669,7 +3688,7 @@ class SkillsLoadedSkill: description: str enabled: bool name: str - source: str + source: SkillSource user_invocable: bool path: str | None = None @@ -3679,7 +3698,7 @@ def from_dict(obj: Any) -> "SkillsLoadedSkill": description = from_str(obj.get("description")) enabled = from_bool(obj.get("enabled")) name = from_str(obj.get("name")) - source = from_str(obj.get("source")) + source = parse_enum(SkillSource, obj.get("source")) user_invocable = from_bool(obj.get("userInvocable")) path = from_union([from_none, from_str], obj.get("path")) return SkillsLoadedSkill( @@ -3696,7 +3715,7 @@ def to_dict(self) -> 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["source"] = to_enum(SkillSource, self.source) result["userInvocable"] = from_bool(self.user_invocable) if self.path is not None: result["path"] = from_union([from_none, from_str], self.path) @@ -3709,7 +3728,7 @@ class SubagentCompletedData: agent_display_name: str agent_name: str tool_call_id: str - duration_ms: float | None = None + duration_ms: timedelta | None = None model: str | None = None total_tokens: float | None = None total_tool_calls: float | None = None @@ -3720,7 +3739,7 @@ def from_dict(obj: Any) -> "SubagentCompletedData": agent_display_name = from_str(obj.get("agentDisplayName")) agent_name = from_str(obj.get("agentName")) tool_call_id = from_str(obj.get("toolCallId")) - duration_ms = from_union([from_none, from_float], obj.get("durationMs")) + duration_ms = from_union([from_none, from_timedelta], obj.get("durationMs")) model = from_union([from_none, from_str], obj.get("model")) total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) @@ -3740,7 +3759,7 @@ def to_dict(self) -> dict: result["agentName"] = from_str(self.agent_name) result["toolCallId"] = from_str(self.tool_call_id) if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + result["durationMs"] = from_union([from_none, to_timedelta], self.duration_ms) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.total_tokens is not None: @@ -3769,7 +3788,7 @@ class SubagentFailedData: agent_name: str error: str tool_call_id: str - duration_ms: float | None = None + duration_ms: timedelta | None = None model: str | None = None total_tokens: float | None = None total_tool_calls: float | None = None @@ -3781,7 +3800,7 @@ def from_dict(obj: Any) -> "SubagentFailedData": agent_name = from_str(obj.get("agentName")) error = from_str(obj.get("error")) tool_call_id = from_str(obj.get("toolCallId")) - duration_ms = from_union([from_none, from_float], obj.get("durationMs")) + duration_ms = from_union([from_none, from_timedelta], obj.get("durationMs")) model = from_union([from_none, from_str], obj.get("model")) total_tokens = from_union([from_none, from_float], obj.get("totalTokens")) total_tool_calls = from_union([from_none, from_float], obj.get("totalToolCalls")) @@ -3803,7 +3822,7 @@ def to_dict(self) -> dict: result["error"] = from_str(self.error) result["toolCallId"] = from_str(self.tool_call_id) if self.duration_ms is not None: - result["durationMs"] = from_union([from_none, to_float], self.duration_ms) + result["durationMs"] = from_union([from_none, to_timedelta], self.duration_ms) if self.model is not None: result["model"] = from_union([from_none, from_str], self.model) if self.total_tokens is not None: @@ -4829,6 +4848,13 @@ class AssistantUsageApiEndpoint(Enum): WS_RESPONSES = "ws:/responses" +class AutoModeSwitchResponse(Enum): + "The user's auto-mode-switch choice" + YES = "yes" + YES_ALWAYS = "yes_always" + NO = "no" + + class ElicitationCompletedAction(Enum): "The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)" ACCEPT = "accept" @@ -4842,6 +4868,14 @@ class ElicitationRequestedMode(Enum): URL = "url" +class ExitPlanModeAction(Enum): + "Exit plan mode action" + EXIT_ONLY = "exit_only" + INTERACTIVE = "interactive" + AUTOPILOT = "autopilot" + AUTOPILOT_FLEET = "autopilot_fleet" + + class ExtensionsLoadedExtensionSource(Enum): "Discovery source" PROJECT = "project" @@ -4862,17 +4896,15 @@ class HandoffSourceType(Enum): LOCAL = "local" -class McpServerStatusChangedStatus(Enum): - "New connection status: connected, failed, needs-auth, pending, disabled, or not_configured" - CONNECTED = "connected" - FAILED = "failed" - NEEDS_AUTH = "needs-auth" - PENDING = "pending" - DISABLED = "disabled" - NOT_CONFIGURED = "not_configured" +class McpServerSource(Enum): + "Configuration source: user, workspace, plugin, or builtin" + USER = "user" + WORKSPACE = "workspace" + PLUGIN = "plugin" + BUILTIN = "builtin" -class McpServersLoadedServerStatus(Enum): +class McpServerStatus(Enum): "Connection status: connected, failed, needs-auth, pending, disabled, or not_configured" CONNECTED = "connected" FAILED = "failed" @@ -4904,18 +4936,6 @@ class PermissionPromptRequestKind(Enum): EXTENSION_PERMISSION_ACCESS = "extension-permission-access" -class PermissionPromptRequestMemoryAction(Enum): - "Whether this is a store or vote memory operation" - STORE = "store" - VOTE = "vote" - - -class PermissionPromptRequestMemoryDirection(Enum): - "Vote direction (vote only)" - UPVOTE = "upvote" - DOWNVOTE = "downvote" - - class PermissionPromptRequestPathAccessKind(Enum): "Underlying permission kind that needs path approval" READ = "read" @@ -4976,12 +4996,30 @@ class ReasoningSummary(Enum): DETAILED = "detailed" +class SessionMode(Enum): + "The session mode the agent is operating in" + INTERACTIVE = "interactive" + PLAN = "plan" + AUTOPILOT = "autopilot" + + class ShutdownType(Enum): "Whether the session ended normally (\"routine\") or due to a crash/fatal error (\"error\")" ROUTINE = "routine" ERROR = "error" +class SkillSource(Enum): + "Source location type (e.g., project, personal-copilot, plugin, builtin)" + PROJECT = "project" + INHERITED = "inherited" + PERSONAL_COPILOT = "personal-copilot" + PERSONAL_AGENTS = "personal-agents" + PLUGIN = "plugin" + CUSTOM = "custom" + BUILTIN = "builtin" + + class SystemMessageRole(Enum): "Message role: \"system\" for system prompts, \"developer\" for developer-injected instructions" SYSTEM = "system" diff --git a/python/copilot/session_fs_provider.py b/python/copilot/session_fs_provider.py index 5435d3b56..eb8882336 100644 --- a/python/copilot/session_fs_provider.py +++ b/python/copilot/session_fs_provider.py @@ -21,6 +21,7 @@ from collections.abc import Sequence from dataclasses import dataclass from datetime import UTC, datetime +from typing import Any from .generated.rpc import ( SessionFSError, @@ -31,6 +32,9 @@ SessionFSReaddirWithTypesEntry, SessionFSReaddirWithTypesResult, SessionFSReadFileResult, + SessionFSSqliteExistsResult, + SessionFSSqliteQueryResult, + SessionFSSqliteQueryType, SessionFSStatResult, ) @@ -95,6 +99,20 @@ async def rm(self, path: str, recursive: bool, force: bool) -> None: async def rename(self, src: str, dest: str) -> None: """Rename / move a file or directory.""" + @abc.abstractmethod + async def sqlite_query( + self, + session_id: str, + query: str, + query_type: SessionFSSqliteQueryType, + params: dict[str, float | str | None] | None = None, + ) -> SessionFSSqliteQueryResult: + """Execute a SQLite query against the provider's per-session database.""" + + @abc.abstractmethod + async def sqlite_exists(self, session_id: str) -> bool: + """Return whether the provider has a SQLite database for *session_id*.""" + def create_session_fs_adapter(provider: SessionFsProvider) -> SessionFsHandler: """Wrap a :class:`SessionFsProvider` into a :class:`SessionFsHandler`. @@ -111,7 +129,7 @@ class _SessionFsAdapter: def __init__(self, provider: SessionFsProvider) -> None: self._p = provider - async def read_file(self, params: object) -> SessionFSReadFileResult: + async def read_file(self, params: Any) -> SessionFSReadFileResult: try: content = await self._p.read_file(params.path) # type: ignore[attr-defined] return SessionFSReadFileResult.from_dict({"content": content}) @@ -119,28 +137,28 @@ async def read_file(self, params: object) -> SessionFSReadFileResult: err = _to_session_fs_error(exc) return SessionFSReadFileResult.from_dict({"content": "", "error": err.to_dict()}) - async def write_file(self, params: object) -> SessionFSError | None: + async def write_file(self, params: Any) -> SessionFSError | None: try: await self._p.write_file(params.path, params.content, getattr(params, "mode", None)) # 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: + async def append_file(self, params: Any) -> SessionFSError | None: try: await self._p.append_file(params.path, params.content, getattr(params, "mode", None)) # type: ignore[attr-defined] return None except Exception as exc: return _to_session_fs_error(exc) - async def exists(self, params: object) -> SessionFSExistsResult: + async def exists(self, params: Any) -> 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: + async def stat(self, params: Any) -> SessionFSStatResult: try: info = await self._p.stat(params.path) # type: ignore[attr-defined] return SessionFSStatResult( @@ -162,7 +180,7 @@ async def stat(self, params: object) -> SessionFSStatResult: error=err, ) - async def mkdir(self, params: object) -> SessionFSError | None: + async def mkdir(self, params: Any) -> SessionFSError | None: try: await self._p.mkdir( params.path, # type: ignore[attr-defined] @@ -173,7 +191,7 @@ async def mkdir(self, params: object) -> SessionFSError | None: except Exception as exc: return _to_session_fs_error(exc) - async def readdir(self, params: object) -> SessionFSReaddirResult: + async def readdir(self, params: Any) -> SessionFSReaddirResult: try: entries = await self._p.readdir(params.path) # type: ignore[attr-defined] return SessionFSReaddirResult.from_dict({"entries": entries}) @@ -181,7 +199,7 @@ async def readdir(self, params: object) -> SessionFSReaddirResult: err = _to_session_fs_error(exc) return SessionFSReaddirResult.from_dict({"entries": [], "error": err.to_dict()}) - async def readdir_with_types(self, params: object) -> SessionFSReaddirWithTypesResult: + async def readdir_with_types(self, params: Any) -> SessionFSReaddirWithTypesResult: try: entries = await self._p.readdir_with_types(params.path) # type: ignore[attr-defined] return SessionFSReaddirWithTypesResult(entries=list(entries)) @@ -191,7 +209,7 @@ async def readdir_with_types(self, params: object) -> SessionFSReaddirWithTypesR {"entries": [], "error": err.to_dict()} ) - async def rm(self, params: object) -> SessionFSError | None: + async def rm(self, params: Any) -> SessionFSError | None: try: await self._p.rm( params.path, # type: ignore[attr-defined] @@ -202,13 +220,36 @@ async def rm(self, params: object) -> SessionFSError | None: except Exception as exc: return _to_session_fs_error(exc) - async def rename(self, params: object) -> SessionFSError | None: + async def rename(self, params: Any) -> 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) + async def sqlite_query(self, params: Any) -> SessionFSSqliteQueryResult: + try: + return await self._p.sqlite_query( # type: ignore[attr-defined] + params.session_id, + params.query, + params.query_type, + getattr(params, "params", None), + ) + except Exception as exc: + return SessionFSSqliteQueryResult( + columns=[], + rows=[], + rows_affected=0, + error=_to_session_fs_error(exc), + ) + + async def sqlite_exists(self, params: Any) -> SessionFSSqliteExistsResult: + try: + result = await self._p.sqlite_exists(params.session_id) # type: ignore[attr-defined] + return SessionFSSqliteExistsResult.from_dict({"exists": result}) + except Exception: + return SessionFSSqliteExistsResult.from_dict({"exists": False}) + def _to_session_fs_error(exc: Exception) -> SessionFSError: code = SessionFSErrorCode.ENOENT if _is_enoent(exc) else SessionFSErrorCode.UNKNOWN diff --git a/python/e2e/test_mode_handlers_e2e.py b/python/e2e/test_mode_handlers_e2e.py index 981a9ca8b..c0e19da13 100644 --- a/python/e2e/test_mode_handlers_e2e.py +++ b/python/e2e/test_mode_handlers_e2e.py @@ -9,6 +9,8 @@ from copilot.generated.session_events import ( AutoModeSwitchCompletedData, AutoModeSwitchRequestedData, + AutoModeSwitchResponse, + ExitPlanModeAction, ExitPlanModeCompletedData, ExitPlanModeRequestedData, SessionIdleData, @@ -104,7 +106,7 @@ async def on_exit_plan_mode(request, invocation): lambda event: ( isinstance(event.data, ExitPlanModeCompletedData) and event.data.approved is True - and event.data.selected_action == "interactive" + and event.data.selected_action == ExitPlanModeAction.INTERACTIVE ), ) ) @@ -126,7 +128,7 @@ async def on_exit_plan_mode(request, invocation): completed = await completed_event assert completed.data.approved is True - assert completed.data.selected_action == "interactive" + assert completed.data.selected_action == ExitPlanModeAction.INTERACTIVE assert completed.data.feedback == "Approved by the Python E2E test" assert response is not None finally: @@ -164,7 +166,7 @@ async def on_auto_mode_switch(request, invocation): session, lambda event: ( isinstance(event.data, AutoModeSwitchCompletedData) - and event.data.response == "yes" + and event.data.response == AutoModeSwitchResponse.YES ), ) ) @@ -192,7 +194,7 @@ async def on_auto_mode_switch(request, invocation): assert requested.data.retry_after_seconds == 1 completed = await completed_event - assert completed.data.response == "yes" + assert completed.data.response == AutoModeSwitchResponse.YES model_change = await model_change_event assert model_change.data.cause == "rate_limit_auto_switch" diff --git a/python/e2e/test_per_session_auth_e2e.py b/python/e2e/test_per_session_auth_e2e.py index 0f07824c1..7bc32bce2 100644 --- a/python/e2e/test_per_session_auth_e2e.py +++ b/python/e2e/test_per_session_auth_e2e.py @@ -2,6 +2,7 @@ import pytest +from copilot.client import CopilotClient, SubprocessConfig from copilot.session import PermissionHandler from .testharness import E2ETestContext @@ -93,18 +94,32 @@ async def test_should_isolate_auth_between_sessions_with_different_tokens( async def test_should_return_unauthenticated_when_no_token_provided( self, auth_ctx: E2ETestContext ): - session = await auth_ctx.client.create_session( - on_permission_request=PermissionHandler.approve_all, + env = without_auth_env(auth_ctx.get_env()) + env["COPILOT_DEBUG_GITHUB_API_URL"] = auth_ctx.proxy_url + no_token_client = CopilotClient( + SubprocessConfig( + cli_path=auth_ctx.cli_path, + cwd=auth_ctx.work_dir, + env=env, + use_logged_in_user=False, + ) ) - auth_status = await session.rpc.auth.get_status() - # Without a per-session token, there is no per-session identity. - # In CI the process-level fake token may still authenticate globally, - # so we check login rather than is_authenticated. On some platforms - # the absence of a login may surface as None, on others as an empty string. - assert not auth_status.login + try: + session = await no_token_client.create_session( + on_permission_request=PermissionHandler.approve_all, + ) - await session.disconnect() + auth_status = await session.rpc.auth.get_status() + # Without a per-session token, there is no per-session identity. + # In CI the process-level fake token may still authenticate globally, + # so we check login rather than is_authenticated. On some platforms + # the absence of a login may surface as None, on others as an empty string. + assert not auth_status.login + + await session.disconnect() + finally: + await no_token_client.stop() async def test_should_error_when_creating_session_with_invalid_token( self, auth_ctx: E2ETestContext @@ -114,3 +129,12 @@ async def test_should_error_when_creating_session_with_invalid_token( on_permission_request=PermissionHandler.approve_all, github_token="invalid-token-12345", ) + + +def without_auth_env(env: dict[str, str]) -> dict[str, str]: + return { + **env, + "COPILOT_SDK_AUTH_TOKEN": "", + "GH_TOKEN": "", + "GITHUB_TOKEN": "", + } diff --git a/python/e2e/test_rpc_event_side_effects_e2e.py b/python/e2e/test_rpc_event_side_effects_e2e.py index e31e00fbe..b4a5b2790 100644 --- a/python/e2e/test_rpc_event_side_effects_e2e.py +++ b/python/e2e/test_rpc_event_side_effects_e2e.py @@ -69,8 +69,8 @@ def on_event(event): event = await asyncio.wait_for(changed_future, timeout=15.0) assert isinstance(event.data, SessionModeChangedData) - assert event.data.new_mode == SessionMode.PLAN.value - assert event.data.previous_mode == SessionMode.INTERACTIVE.value + assert event.data.new_mode == SessionMode.PLAN + assert event.data.previous_mode == SessionMode.INTERACTIVE finally: unsubscribe() finally: diff --git a/python/e2e/test_rpc_mcp_config_e2e.py b/python/e2e/test_rpc_mcp_config_e2e.py index bab0a62a8..d9229adff 100644 --- a/python/e2e/test_rpc_mcp_config_e2e.py +++ b/python/e2e/test_rpc_mcp_config_e2e.py @@ -19,7 +19,7 @@ MCPConfigUpdateRequest, MCPServerConfig, MCPServerConfigHTTPOauthGrantType, - MCPServerConfigType, + MCPServerConfigHTTPType, ) from .testharness import E2ETestContext @@ -71,7 +71,7 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon server_name = f"sdk-http-oauth-{uuid.uuid4().hex}" config = MCPServerConfig( - type=MCPServerConfigType.HTTP, + type=MCPServerConfigHTTPType.HTTP, url="https://example.com/mcp", headers={"Authorization": "Bearer token"}, oauth_client_id="client-id", @@ -81,7 +81,7 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon timeout=3000, ) updated_config = MCPServerConfig( - type=MCPServerConfigType.HTTP, + type=MCPServerConfigHTTPType.HTTP, url="https://example.com/updated-mcp", oauth_client_id="updated-client-id", oauth_public_client=True, @@ -96,7 +96,7 @@ async def test_should_round_trip_http_mcp_oauth_config_rpc(self, ctx: E2ETestCon ) after_add = await ctx.client.rpc.mcp.config.list() added = _server_config(after_add.servers, server_name) - assert added.type == MCPServerConfigType.HTTP + assert added.type == MCPServerConfigHTTPType.HTTP assert added.url == "https://example.com/mcp" assert added.headers is not None assert added.headers["Authorization"] == "Bearer token" diff --git a/python/e2e/test_session_fs_e2e.py b/python/e2e/test_session_fs_e2e.py index e8fd85ca7..328ad9e02 100644 --- a/python/e2e/test_session_fs_e2e.py +++ b/python/e2e/test_session_fs_e2e.py @@ -17,12 +17,14 @@ from copilot.generated.rpc import ( SessionFSReaddirWithTypesEntry, SessionFSReaddirWithTypesEntryType, + SessionFSSqliteQueryResult, + SessionFSSqliteQueryType, ) 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 +from .testharness import DEFAULT_GITHUB_TOKEN, E2ETestContext pytestmark = pytest.mark.asyncio(loop_scope="module") @@ -44,15 +46,12 @@ @pytest_asyncio.fixture(scope="module", loop_scope="module") async def session_fs_client(ctx: E2ETestContext): - github_token = ( - "fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None - ) client = CopilotClient( SubprocessConfig( cli_path=ctx.cli_path, cwd=ctx.work_dir, env=ctx.get_env(), - github_token=github_token, + github_token=DEFAULT_GITHUB_TOKEN, session_fs=SESSION_FS_CONFIG, ) ) @@ -119,16 +118,13 @@ async def test_should_load_session_data_from_fs_provider_on_resume( await session2.disconnect() async def test_should_reject_setprovider_when_sessions_already_exist(self, ctx: E2ETestContext): - github_token = ( - "fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None - ) client1 = CopilotClient( SubprocessConfig( cli_path=ctx.cli_path, cwd=ctx.work_dir, env=ctx.get_env(), use_stdio=False, - github_token=github_token, + github_token=DEFAULT_GITHUB_TOKEN, ) ) session = None @@ -287,6 +283,8 @@ async def test_should_map_all_sessionfs_handler_operations(self, ctx: E2ETestCon SessionFSReadFileRequest, SessionFSRenameRequest, SessionFSRmRequest, + SessionFSSqliteExistsRequest, + SessionFSSqliteQueryRequest, SessionFSStatRequest, SessionFSWriteFileRequest, ) @@ -397,6 +395,31 @@ async def test_should_map_all_sessionfs_handler_operations(self, ctx: E2ETestCon from copilot.generated.rpc import SessionFSErrorCode assert missing.error.code == SessionFSErrorCode.ENOENT + + sqlite_query = await handler.sqlite_query( + SessionFSSqliteQueryRequest( + session_id=session_id, + query="select :answer as answer", + query_type=SessionFSSqliteQueryType.QUERY, + params={"answer": 42}, + ) + ) + assert "answer" in sqlite_query.columns + assert sqlite_query.rows == [ + { + "sessionId": session_id, + "query": "select :answer as answer", + "queryType": "query", + "answer": 42, + } + ] + assert sqlite_query.rows_affected == 0 + assert sqlite_query.error is None + + sqlite_exists = await handler.sqlite_exists( + SessionFSSqliteExistsRequest(session_id=session_id) + ) + assert sqlite_exists.exists is True finally: try: import shutil @@ -416,6 +439,8 @@ async def test_sessionfsprovider_converts_exceptions_to_rpc_errors(self): SessionFSReadFileRequest, SessionFSRenameRequest, SessionFSRmRequest, + SessionFSSqliteExistsRequest, + SessionFSSqliteQueryRequest, SessionFSStatRequest, SessionFSWriteFileRequest, ) @@ -455,6 +480,12 @@ async def rm(self, path, recursive, force): async def rename(self, src, dest): raise self._exc + async def sqlite_query(self, session_id, query, query_type, params=None): + raise self._exc + + async def sqlite_exists(self, session_id): + raise self._exc + def assert_fs_error(error) -> None: assert error is not None assert error.code == SessionFSErrorCode.ENOENT @@ -511,6 +542,17 @@ def assert_fs_error(error) -> None: SessionFSRenameRequest(session_id=sid, src="missing.txt", dest="dest.txt") ) ) + sqlite_query = await handler.sqlite_query( + SessionFSSqliteQueryRequest( + session_id=sid, query="select 1", query_type=SessionFSSqliteQueryType.QUERY + ) + ) + assert_fs_error(sqlite_query.error) + assert sqlite_query.columns == [] + assert sqlite_query.rows == [] + assert sqlite_query.rows_affected == 0 + sqlite_exists = await handler.sqlite_exists(SessionFSSqliteExistsRequest(session_id=sid)) + assert sqlite_exists.exists is False unknown_handler = create_session_fs_adapter(_ThrowingProvider(RuntimeError("bad path"))) unknown_error = await unknown_handler.write_file( @@ -588,6 +630,29 @@ async def rename(self, src: str, dest: str) -> None: d.parent.mkdir(parents=True, exist_ok=True) self._path(src).rename(d) + async def sqlite_query( + self, + session_id: str, + query: str, + query_type: SessionFSSqliteQueryType, + params: dict[str, float | str | None] | None = None, + ) -> SessionFSSqliteQueryResult: + return SessionFSSqliteQueryResult( + columns=["sessionId", "query", "queryType", "answer"], + rows=[ + { + "sessionId": session_id, + "query": query, + "queryType": query_type.value, + "answer": params["answer"] if params else None, + } + ], + rows_affected=0, + ) + + async def sqlite_exists(self, session_id: str) -> bool: + return session_id == self._session_id + def create_test_session_fs_handler(provider_root: Path): def create_handler(session): diff --git a/python/e2e/test_skills_e2e.py b/python/e2e/test_skills_e2e.py index 368b42379..c31632fda 100644 --- a/python/e2e/test_skills_e2e.py +++ b/python/e2e/test_skills_e2e.py @@ -7,6 +7,7 @@ import pytest +from copilot.generated.rpc import SkillSource from copilot.session import CustomAgentConfig, PermissionHandler from .testharness import E2ETestContext @@ -230,6 +231,6 @@ async def test_should_control_ambient_project_skills_with_enableconfigdiscovery( assert len(discovered) == 1 skill = discovered[0] assert skill.enabled is True - assert skill.source == "project" + assert skill.source == SkillSource.PROJECT assert skill.path.endswith(os.path.join(skill_name, "SKILL.md")) await enabled_session.disconnect() diff --git a/python/e2e/testharness/__init__.py b/python/e2e/testharness/__init__.py index 58a36028f..28558d687 100644 --- a/python/e2e/testharness/__init__.py +++ b/python/e2e/testharness/__init__.py @@ -1,11 +1,12 @@ """Test harness for E2E tests.""" -from .context import CLI_PATH, E2ETestContext +from .context import CLI_PATH, DEFAULT_GITHUB_TOKEN, E2ETestContext from .helper import get_final_assistant_message, get_next_event_of_type from .proxy import CapiProxy __all__ = [ "CLI_PATH", + "DEFAULT_GITHUB_TOKEN", "E2ETestContext", "CapiProxy", "get_final_assistant_message", diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index acebe5f91..dc31cfe92 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -38,6 +38,7 @@ def get_cli_path_for_tests() -> str: CLI_PATH = get_cli_path_for_tests() SNAPSHOTS_DIR = Path(__file__).parents[3] / "test" / "snapshots" +DEFAULT_GITHUB_TOKEN = "fake-token-for-e2e-tests" class E2ETestContext: @@ -64,19 +65,27 @@ async def setup(self, cli_args: list[str] | None = None): self._proxy = CapiProxy() self.proxy_url = await self._proxy.start() + await self._proxy.set_copilot_user_by_token( + DEFAULT_GITHUB_TOKEN, + { + "login": "e2e-test-user", + "copilot_plan": "individual_pro", + "endpoints": { + "api": self.proxy_url, + "telemetry": "https://localhost:1/telemetry", + }, + "analytics_tracking_id": "e2e-test-tracking-id", + }, + ) # Create the shared client (like Node.js/Go do) - # Use fake token in CI to allow cached responses without real auth - github_token = ( - "fake-token-for-e2e-tests" if os.environ.get("GITHUB_ACTIONS") == "true" else None - ) self._client = CopilotClient( SubprocessConfig( cli_path=self.cli_path, cli_args=cli_args or [], cwd=self.work_dir, env=self.get_env(), - github_token=github_token, + github_token=DEFAULT_GITHUB_TOKEN, ) ) @@ -140,14 +149,14 @@ def get_env(self) -> dict: { "COPILOT_API_URL": self.proxy_url, "COPILOT_HOME": self.home_dir, + "COPILOT_SDK_AUTH_TOKEN": DEFAULT_GITHUB_TOKEN, "GH_CONFIG_DIR": self.home_dir, + "GH_TOKEN": DEFAULT_GITHUB_TOKEN, "XDG_CONFIG_HOME": self.home_dir, "XDG_STATE_HOME": self.home_dir, + "GITHUB_TOKEN": DEFAULT_GITHUB_TOKEN, } ) - if os.environ.get("GITHUB_ACTIONS") == "true": - env["GH_TOKEN"] = "fake-token-for-e2e-tests" - env["GITHUB_TOKEN"] = "fake-token-for-e2e-tests" return env @property diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs index 604f649b1..6f7e97ad7 100644 --- a/rust/src/generated/api_types.rs +++ b/rust/src/generated/api_types.rs @@ -6,7 +6,9 @@ use std::collections::HashMap; use serde::{Deserialize, Serialize}; -use super::session_events::ReasoningSummary; +use super::session_events::{ + McpServerSource, McpServerStatus, ReasoningSummary, SessionMode, SkillSource, +}; use crate::types::{RequestId, SessionId}; /// JSON-RPC method name constants. @@ -188,6 +190,10 @@ pub mod rpc_methods { pub const SESSIONFS_RM: &str = "sessionFs.rm"; /// `sessionFs.rename` pub const SESSIONFS_RENAME: &str = "sessionFs.rename"; + /// `sessionFs.sqliteQuery` + pub const SESSIONFS_SQLITEQUERY: &str = "sessionFs.sqliteQuery"; + /// `sessionFs.sqliteExists` + pub const SESSIONFS_SQLITEEXISTS: &str = "sessionFs.sqliteExists"; } /// Optional GitHub token used to look up quota for a specific user instead of the global auth context. @@ -203,7 +209,7 @@ pub struct AccountGetQuotaRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AccountQuotaSnapshot { - /// Number of requests included in the entitlement + /// Number of requests included in the entitlement, or -1 for unlimited entitlements pub entitlement_requests: i64, /// Whether the user has an unlimited usage entitlement pub is_unlimited_entitlement: bool, @@ -551,9 +557,9 @@ pub struct DiscoveredMcpServer { pub enabled: bool, /// Server name (config key) pub name: String, - /// Configuration source - pub source: DiscoveredMcpServerSource, - /// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) + /// Configuration source: user, workspace, plugin, or builtin + pub source: McpServerSource, + /// Server transport type: stdio, http, sse, or memory #[serde(skip_serializing_if = "Option::is_none")] pub r#type: Option, } @@ -627,10 +633,28 @@ pub struct ExtensionsEnableRequest { pub id: String, } +/// Binary result returned by a tool for the model +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ExternalToolTextResultForLlmBinaryResultsForLlm { + /// Base64-encoded binary data + pub data: String, + /// Human-readable description of the binary data + #[serde(skip_serializing_if = "Option::is_none")] + pub description: Option, + /// MIME type of the binary data + pub mime_type: String, + /// Binary result type discriminator. Use "image" for images and "resource" for other binary data. + pub r#type: ExternalToolTextResultForLlmBinaryResultsForLlmType, +} + /// Expanded external tool result payload #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExternalToolTextResultForLlm { + /// Base64-encoded binary results returned to the model + #[serde(default)] + pub binary_results_for_llm: Vec, /// Structured content blocks from the tool #[serde(default)] pub contents: Vec, @@ -948,7 +972,7 @@ pub struct LogResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpConfigAddRequest { - /// MCP server configuration (local/stdio or remote/http) + /// MCP server configuration (stdio process or remote HTTP/SSE) pub config: serde_json::Value, /// Unique name for the MCP server pub name: String, @@ -990,7 +1014,7 @@ pub struct McpConfigRemoveRequest { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpConfigUpdateRequest { - /// MCP server configuration (local/stdio or remote/http) + /// MCP server configuration (stdio process or remote HTTP/SSE) pub config: serde_json::Value, /// Name of the MCP server to update pub name: String, @@ -1106,10 +1130,22 @@ pub struct McpServer { pub status: McpServerStatus, } +/// Additional authentication configuration for this server. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct McpServerConfigHttpAuth { + /// Fixed port for the OAuth redirect callback server. + #[serde(skip_serializing_if = "Option::is_none")] + pub redirect_port: Option, +} + /// Remote MCP server configuration accessed over HTTP or SSE. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct McpServerConfigHttp { + /// Additional authentication configuration for this server. + #[serde(skip_serializing_if = "Option::is_none")] + pub auth: Option, /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. #[serde(skip_serializing_if = "Option::is_none")] pub filter_mapping: Option, @@ -1141,18 +1177,19 @@ pub struct McpServerConfigHttp { pub url: String, } -/// Local MCP server configuration launched as a child process. +/// Stdio MCP server configuration launched as a child process. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct McpServerConfigLocal { - /// Command-line arguments passed to the local MCP server process. +pub struct McpServerConfigStdio { + /// Command-line arguments passed to the Stdio MCP server process. + #[serde(default)] pub args: Vec, - /// Executable command used to start the local MCP server process. + /// Executable command used to start the Stdio MCP server process. pub command: String, - /// Working directory for the local MCP server process. + /// Working directory for the Stdio MCP server process. #[serde(skip_serializing_if = "Option::is_none")] pub cwd: Option, - /// Environment variables to pass to the local MCP server process. + /// Environment variables to pass to the Stdio MCP server process. #[serde(default)] pub env: HashMap, /// Content filtering mode to apply to all tools, or a map of tool name to content filtering mode. @@ -1167,9 +1204,6 @@ pub struct McpServerConfigLocal { /// Tools to include. Defaults to all tools if not specified. #[serde(default)] pub tools: Vec, - /// Local transport type. Defaults to "local". - #[serde(skip_serializing_if = "Option::is_none")] - pub r#type: Option, } /// MCP servers configured for the session, with their connection status. @@ -1282,7 +1316,7 @@ pub struct ModelCapabilities { #[serde(rename_all = "camelCase")] pub struct ModelPolicy { /// Current policy state for this model - pub state: String, + pub state: ModelPolicyState, /// Usage terms or conditions for this model #[serde(skip_serializing_if = "Option::is_none")] pub terms: Option, @@ -1428,7 +1462,7 @@ pub struct ModelSwitchToResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ModeSetRequest { - /// The agent mode. Valid values: "interactive", "plan", "autopilot". + /// The session mode the agent is operating in pub mode: SessionMode, } @@ -1897,7 +1931,7 @@ pub struct ServerSkill { #[serde(skip_serializing_if = "Option::is_none")] pub project_path: Option, /// Source location type (e.g., project, personal-copilot, plugin, builtin) - pub source: String, + pub source: SkillSource, /// Whether the skill can be invoked by the user as a slash command pub user_invocable: bool, } @@ -1937,13 +1971,15 @@ pub struct SessionAuthStatus { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsAppendFileRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, /// Content to append pub content: String, /// Optional POSIX-style mode for newly created files #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, - /// Path using SessionFs conventions - pub path: String, } /// Describes a filesystem error. @@ -1961,6 +1997,8 @@ pub struct SessionFsError { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsExistsRequest { + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, } @@ -1977,20 +2015,24 @@ pub struct SessionFsExistsResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsMkdirRequest { - /// Optional POSIX-style mode for newly created directories - #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, /// Create parent directories as needed #[serde(skip_serializing_if = "Option::is_none")] pub recursive: Option, + /// Optional POSIX-style mode for newly created directories + #[serde(skip_serializing_if = "Option::is_none")] + pub mode: Option, } /// Directory path whose entries should be listed from the client-provided session filesystem. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsReaddirRequest { + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, } @@ -2020,6 +2062,8 @@ pub struct SessionFsReaddirWithTypesEntry { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsReaddirWithTypesRequest { + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, } @@ -2039,6 +2083,8 @@ pub struct SessionFsReaddirWithTypesResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsReadFileRequest { + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, } @@ -2058,30 +2104,46 @@ pub struct SessionFsReadFileResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsRenameRequest { - /// Destination path using SessionFs conventions - pub dest: String, + /// Target session identifier + pub session_id: SessionId, /// Source path using SessionFs conventions pub src: String, + /// Destination path using SessionFs conventions + pub dest: String, } /// Path to remove from the client-provided session filesystem, with options for recursive removal and force. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsRmRequest { - /// Ignore errors if the path does not exist - #[serde(skip_serializing_if = "Option::is_none")] - pub force: Option, + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, /// Remove directories and their contents recursively #[serde(skip_serializing_if = "Option::is_none")] pub recursive: Option, + /// Ignore errors if the path does not exist + #[serde(skip_serializing_if = "Option::is_none")] + pub force: Option, +} + +/// Optional capabilities declared by the provider +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSetProviderCapabilities { + /// Whether the provider supports SQLite query/exists operations + #[serde(skip_serializing_if = "Option::is_none")] + pub sqlite: Option, } /// Initial working directory, session-state path layout, and path conventions used to register the calling SDK client as the session filesystem provider. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsSetProviderRequest { + /// Optional capabilities declared by the provider + #[serde(skip_serializing_if = "Option::is_none")] + pub capabilities: Option, /// Path conventions used by this filesystem pub conventions: SessionFsSetProviderConventions, /// Initial working directory for sessions @@ -2098,10 +2160,53 @@ pub struct SessionFsSetProviderResult { pub success: bool, } +/// Indicates whether the per-session SQLite database already exists. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteExistsResult { + /// Whether the session database already exists + pub exists: bool, +} + +/// SQL query, query type, and optional bind parameters for executing a SQLite query against the per-session database. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteQueryRequest { + /// Target session identifier + pub session_id: SessionId, + /// SQL query to execute + pub query: String, + /// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) + pub query_type: SessionFsSqliteQueryType, + /// Optional named bind parameters + #[serde(default)] + pub params: HashMap, +} + +/// Query results including rows, columns, and rows affected, or a filesystem error if execution failed. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteQueryResult { + /// Column names from the result set + pub columns: Vec, + /// Describes a filesystem error. + #[serde(skip_serializing_if = "Option::is_none")] + pub error: Option, + /// Last inserted row ID (for INSERT) + #[serde(skip_serializing_if = "Option::is_none")] + pub last_insert_rowid: Option, + /// For SELECT: array of row objects. For others: empty array. + pub rows: Vec>, + /// Number of rows affected (for INSERT/UPDATE/DELETE) + pub rows_affected: i64, +} + /// Path whose metadata should be returned from the client-provided session filesystem. #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsStatRequest { + /// Target session identifier + pub session_id: SessionId, /// Path using SessionFs conventions pub path: String, } @@ -2129,13 +2234,15 @@ pub struct SessionFsStatResult { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionFsWriteFileRequest { + /// Target session identifier + pub session_id: SessionId, + /// Path using SessionFs conventions + pub path: String, /// Content to write pub content: String, /// Optional POSIX-style mode for newly created files #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, - /// Path using SessionFs conventions - pub path: String, } /// Source session identifier to fork from, optional event-ID boundary, and optional friendly name for the new session. @@ -2238,8 +2345,8 @@ pub struct Skill { /// Absolute path to the skill file #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, - /// Source location type (e.g., project, personal, plugin) - pub source: String, + /// Source location type (e.g., project, personal-copilot, plugin, builtin) + pub source: SkillSource, /// Whether the skill can be invoked by the user as a slash command pub user_invocable: bool, } @@ -2334,9 +2441,9 @@ pub struct SlashCommandAgentPromptResult { pub display_prompt: String, /// Agent prompt result discriminator pub kind: SlashCommandAgentPromptResultKind, - /// Optional target session mode + /// Optional target session mode for the agent prompt #[serde(skip_serializing_if = "Option::is_none")] - pub mode: Option, + pub mode: Option, /// Prompt to submit to the agent pub prompt: String, /// True when the invocation mutated user runtime settings; consumers caching settings should refresh @@ -2407,9 +2514,9 @@ pub struct TaskAgentInfo { /// Error message when the task failed #[serde(skip_serializing_if = "Option::is_none")] pub error: Option, - /// How the agent is currently being managed by the runtime + /// Whether task execution is synchronously awaited or managed in the background #[serde(skip_serializing_if = "Option::is_none")] - pub execution_mode: Option, + pub execution_mode: Option, /// Unique task identifier pub id: String, /// ISO 8601 timestamp when the agent entered idle state @@ -2429,7 +2536,7 @@ pub struct TaskAgentInfo { /// ISO 8601 timestamp when the task was started pub started_at: String, /// Current lifecycle status of the task - pub status: TaskAgentInfoStatus, + pub status: TaskStatus, /// Tool call ID associated with this agent task pub tool_call_id: String, /// Task kind @@ -2504,9 +2611,9 @@ pub struct TaskShellInfo { pub completed_at: Option, /// Short description of the task pub description: String, - /// Whether the shell command is currently sync-waited or background-managed + /// Whether task execution is synchronously awaited or managed in the background #[serde(skip_serializing_if = "Option::is_none")] - pub execution_mode: Option, + pub execution_mode: Option, /// Unique task identifier pub id: String, /// Path to the detached shell log, when available @@ -2518,7 +2625,7 @@ pub struct TaskShellInfo { /// ISO 8601 timestamp when the task was started pub started_at: String, /// Current lifecycle status of the task - pub status: TaskShellInfoStatus, + pub status: TaskStatus, /// Task kind pub r#type: TaskShellInfoType, } @@ -4115,6 +4222,14 @@ pub struct SessionRemoteDisableParams { pub session_id: SessionId, } +/// Identifies the target session. +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct SessionFsSqliteExistsParams { + /// Target session identifier + pub session_id: SessionId, +} + /// Authentication type #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum AuthInfoType { @@ -4184,24 +4299,22 @@ pub enum ConnectedRemoteSessionMetadataKind { Unknown, } -/// Configuration source +/// Controls how MCP tool result content is filtered: none leaves content unchanged, markdown sanitizes HTML while preserving Markdown-friendly output, and hidden_characters removes characters that can hide directives. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum DiscoveredMcpServerSource { - #[serde(rename = "user")] - User, - #[serde(rename = "workspace")] - Workspace, - #[serde(rename = "plugin")] - Plugin, - #[serde(rename = "builtin")] - Builtin, +pub enum ContentFilterMode { + #[serde(rename = "none")] + None, + #[serde(rename = "markdown")] + Markdown, + #[serde(rename = "hidden_characters")] + HiddenCharacters, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Server transport type: stdio, http, sse, or memory (local configs are normalized to stdio) +/// Server transport type: stdio, http, sse, or memory #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum DiscoveredMcpServerType { #[serde(rename = "stdio")] @@ -4262,6 +4375,19 @@ pub enum ExtensionStatus { Unknown, } +/// Binary result type discriminator. Use "image" for images and "resource" for other binary data. +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExternalToolTextResultForLlmBinaryResultsForLlmType { + #[serde(rename = "image")] + Image, + #[serde(rename = "resource")] + Resource, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// Content block type discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ExternalToolTextResultForLlmContentAudioType { @@ -4323,36 +4449,6 @@ pub enum ExternalToolTextResultForLlmContentTextType { Text, } -/// Allowed values for the `FilterMappingString` enumeration. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FilterMappingString { - #[serde(rename = "none")] - None, - #[serde(rename = "markdown")] - Markdown, - #[serde(rename = "hidden_characters")] - HiddenCharacters, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - -/// Allowed values for the `FilterMappingValue` enumeration. -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum FilterMappingValue { - #[serde(rename = "none")] - None, - #[serde(rename = "markdown")] - Markdown, - #[serde(rename = "hidden_characters")] - HiddenCharacters, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Where this source lives — used for UI grouping #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum InstructionsSourcesLocation { @@ -4404,58 +4500,6 @@ pub enum SessionLogLevel { Unknown, } -/// Configuration source: user, workspace, plugin, or builtin -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerSource { - #[serde(rename = "user")] - User, - #[serde(rename = "workspace")] - Workspace, - #[serde(rename = "plugin")] - Plugin, - #[serde(rename = "builtin")] - Builtin, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerStatus { - #[serde(rename = "connected")] - Connected, - #[serde(rename = "failed")] - Failed, - #[serde(rename = "needs-auth")] - NeedsAuth, - #[serde(rename = "pending")] - Pending, - #[serde(rename = "disabled")] - Disabled, - #[serde(rename = "not_configured")] - NotConfigured, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// OAuth grant type to use when authenticating to the remote MCP server. #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum McpServerConfigHttpOauthGrantType { @@ -4482,19 +4526,6 @@ pub enum McpServerConfigHttpType { Unknown, } -/// Local transport type. Defaults to "local". -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerConfigLocalType { - #[serde(rename = "local")] - Local, - #[serde(rename = "stdio")] - Stdio, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Model capability category for grouping in the model picker #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum ModelPickerCategory { @@ -4527,15 +4558,15 @@ pub enum ModelPickerPriceCategory { Unknown, } -/// The agent mode. Valid values: "interactive", "plan", "autopilot". +/// Current policy state for this model #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SessionMode { - #[serde(rename = "interactive")] - Interactive, - #[serde(rename = "plan")] - Plan, - #[serde(rename = "autopilot")] - Autopilot, +pub enum ModelPolicyState { + #[serde(rename = "enabled")] + Enabled, + #[serde(rename = "disabled")] + Disabled, + #[serde(rename = "unconfigured")] + Unconfigured, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -4837,27 +4868,27 @@ pub enum SessionFsSetProviderConventions { Unknown, } -/// Signal to send (default: SIGTERM) +/// How to execute the query: 'exec' for DDL/multi-statement (no results), 'query' for SELECT (returns rows), 'run' for INSERT/UPDATE/DELETE (returns rowsAffected) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum ShellKillSignal { - SIGTERM, - SIGKILL, - SIGINT, +pub enum SessionFsSqliteQueryType { + #[serde(rename = "exec")] + Exec, + #[serde(rename = "query")] + Query, + #[serde(rename = "run")] + Run, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// Optional target session mode +/// Signal to send (default: SIGTERM) #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum SlashCommandAgentPromptMode { - #[serde(rename = "interactive")] - Interactive, - #[serde(rename = "plan")] - Plan, - #[serde(rename = "autopilot")] - Autopilot, +pub enum ShellKillSignal { + SIGTERM, + SIGKILL, + SIGINT, /// Unknown variant for forward compatibility. #[default] #[serde(other)] @@ -4897,7 +4928,7 @@ pub enum SlashCommandInvocationResult { Completed(SlashCommandCompletedResult), } -/// How the agent is currently being managed by the runtime +/// Whether task execution is synchronously awaited or managed in the background /// ///
/// @@ -4906,7 +4937,7 @@ pub enum SlashCommandInvocationResult { /// ///
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum TaskAgentInfoExecutionMode { +pub enum TaskExecutionMode { #[serde(rename = "sync")] Sync, #[serde(rename = "background")] @@ -4926,7 +4957,7 @@ pub enum TaskAgentInfoExecutionMode { /// /// #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum TaskAgentInfoStatus { +pub enum TaskStatus { #[serde(rename = "running")] Running, #[serde(rename = "idle")] @@ -4971,52 +5002,6 @@ pub enum TaskShellInfoAttachmentMode { Unknown, } -/// Whether the shell command is currently sync-waited or background-managed -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum TaskShellInfoExecutionMode { - #[serde(rename = "sync")] - Sync, - #[serde(rename = "background")] - Background, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - -/// Current lifecycle status of the task -/// -///
-/// -/// **Experimental.** This type is part of an experimental wire-protocol surface -/// and may change or be removed in future SDK or CLI releases. -/// -///
-#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum TaskShellInfoStatus { - #[serde(rename = "running")] - Running, - #[serde(rename = "idle")] - Idle, - #[serde(rename = "completed")] - Completed, - #[serde(rename = "failed")] - Failed, - #[serde(rename = "cancelled")] - Cancelled, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Task kind #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum TaskShellInfoType { diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs index 519f23f05..dac970fd4 100644 --- a/rust/src/generated/rpc.rs +++ b/rust/src/generated/rpc.rs @@ -9,6 +9,7 @@ #![allow(clippy::too_many_arguments)] use super::api_types::{rpc_methods, *}; +use super::session_events::SessionMode; use crate::session::Session; use crate::{Client, Error}; @@ -1419,7 +1420,7 @@ impl<'a> SessionRpcMode<'a> { /// /// # Returns /// - /// The agent mode. Valid values: "interactive", "plan", "autopilot". + /// The session mode the agent is operating in pub async fn get(&self) -> Result { let wire_params = serde_json::json!({ "sessionId": self.session.id() }); let _value = self diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs index 2c615420c..459c03b77 100644 --- a/rust/src/generated/session_events.rs +++ b/rust/src/generated/session_events.rs @@ -420,7 +420,7 @@ pub struct SessionStartData { pub detached_from_spawning_parent_session_id: Option, /// Identifier of the software producing the events (e.g., "copilot-agent") pub producer: String, - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, /// Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") @@ -455,7 +455,7 @@ pub struct SessionResumeData { pub continue_pending_work: Option, /// Total number of persisted events in the session at the time of resume pub event_count: f64, - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, /// Reasoning summary mode used for model calls, if applicable (e.g. "none", "concise", "detailed") @@ -612,10 +612,10 @@ pub struct SessionModelChangeData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SessionModeChangedData { - /// Agent mode after the change (e.g., "interactive", "plan", "autopilot") - pub new_mode: String, - /// Agent mode before the change (e.g., "interactive", "plan", "autopilot") - pub previous_mode: String, + /// The session mode the agent is operating in + pub new_mode: SessionMode, + /// The session mode the agent is operating in + pub previous_mode: SessionMode, } /// Session event "session.plan_changed". Plan file operation details indicating what changed @@ -1297,7 +1297,7 @@ pub struct AssistantUsageData { /// Per-quota resource usage snapshots, keyed by quota identifier #[serde(default)] pub quota_snapshots: HashMap, - /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh") + /// Reasoning effort level used for model calls, if applicable (e.g. "none", "low", "medium", "high", "xhigh", "max") #[serde(skip_serializing_if = "Option::is_none")] pub reasoning_effort: Option, /// Number of output tokens used for reasoning (e.g., chain-of-thought) @@ -2100,13 +2100,13 @@ pub struct PermissionPromptRequestUrl { pub struct PermissionPromptRequestMemory { /// Whether this is a store or vote memory operation #[serde(skip_serializing_if = "Option::is_none")] - pub action: Option, + pub action: Option, /// Source references for the stored fact (store only) #[serde(skip_serializing_if = "Option::is_none")] pub citations: Option, /// Vote direction (vote only) #[serde(skip_serializing_if = "Option::is_none")] - pub direction: Option, + pub direction: Option, /// The fact being stored or voted on pub fact: String, /// Prompt kind discriminator @@ -2663,8 +2663,8 @@ pub struct AutoModeSwitchRequestedData { pub struct AutoModeSwitchCompletedData { /// Request ID of the resolved request; clients should dismiss any UI for this request pub request_id: RequestId, - /// The user's choice: 'yes', 'yes_always', or 'no' - pub response: String, + /// The user's auto-mode-switch choice + pub response: AutoModeSwitchResponse, } /// Schema for the `CommandsChangedCommand` type. @@ -2708,12 +2708,12 @@ pub struct CapabilitiesChangedData { #[derive(Debug, Clone, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ExitPlanModeRequestedData { - /// Available actions the user can take (e.g., approve, edit, reject) - pub actions: Vec, + /// Available actions the user can take + pub actions: Vec, /// Full content of the plan file pub plan_content: String, - /// The recommended action for the user to take - pub recommended_action: String, + /// Recommended action to preselect for the user + pub recommended_action: ExitPlanModeAction, /// Unique identifier for this request; used to respond via session.respondToExitPlanMode() pub request_id: RequestId, /// Summary of the plan that was created @@ -2735,9 +2735,9 @@ pub struct ExitPlanModeCompletedData { pub feedback: Option, /// Request ID of the resolved exit plan mode request; clients should dismiss any UI for this request pub request_id: RequestId, - /// Which action the user selected (e.g. 'autopilot', 'interactive', 'exit_only') + /// Action selected by the user #[serde(skip_serializing_if = "Option::is_none")] - pub selected_action: Option, + pub selected_action: Option, } /// Session event "session.tools_updated". @@ -2766,8 +2766,8 @@ pub struct SkillsLoadedSkill { /// Absolute path to the skill file, if available #[serde(skip_serializing_if = "Option::is_none")] pub path: Option, - /// Source location type of the skill (e.g., project, personal, plugin) - pub source: String, + /// Source location type (e.g., project, personal-copilot, plugin, builtin) + pub source: SkillSource, /// Whether the skill can be invoked by the user as a slash command pub user_invocable: bool, } @@ -2826,9 +2826,9 @@ pub struct McpServersLoadedServer { pub name: String, /// Configuration source: user, workspace, plugin, or builtin #[serde(skip_serializing_if = "Option::is_none")] - pub source: Option, + pub source: Option, /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured - pub status: McpServersLoadedServerStatus, + pub status: McpServerStatus, } /// Session event "session.mcp_servers_loaded". @@ -2845,8 +2845,8 @@ pub struct SessionMcpServersLoadedData { pub struct SessionMcpServerStatusChangedData { /// Name of the MCP server whose status changed pub server_name: String, - /// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured - pub status: McpServerStatusChangedStatus, + /// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured + pub status: McpServerStatus, } /// Schema for the `ExtensionsLoadedExtension` type. @@ -2899,6 +2899,21 @@ pub enum ReasoningSummary { Unknown, } +/// The session mode the agent is operating in +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SessionMode { + #[serde(rename = "interactive")] + Interactive, + #[serde(rename = "plan")] + Plan, + #[serde(rename = "autopilot")] + Autopilot, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + /// The type of operation performed on the plan file #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PlanChangedOperation { @@ -3286,32 +3301,6 @@ pub enum PermissionPromptRequestUrlKind { Url, } -/// Whether this is a store or vote memory operation -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionPromptRequestMemoryAction { - #[serde(rename = "store")] - Store, - #[serde(rename = "vote")] - Vote, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - -/// Vote direction (vote only) -#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum PermissionPromptRequestMemoryDirection { - #[serde(rename = "upvote")] - Upvote, - #[serde(rename = "downvote")] - Downvote, - /// Unknown variant for forward compatibility. - #[default] - #[serde(other)] - Unknown, -} - /// Prompt kind discriminator #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] pub enum PermissionPromptRequestMemoryKind { @@ -3603,30 +3592,81 @@ pub enum McpOauthRequiredStaticClientConfigGrantType { ClientCredentials, } -/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured +/// The user's auto-mode-switch choice #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServersLoadedServerStatus { - #[serde(rename = "connected")] - Connected, - #[serde(rename = "failed")] - Failed, - #[serde(rename = "needs-auth")] - NeedsAuth, - #[serde(rename = "pending")] - Pending, - #[serde(rename = "disabled")] - Disabled, - #[serde(rename = "not_configured")] - NotConfigured, +pub enum AutoModeSwitchResponse { + #[serde(rename = "yes")] + Yes, + #[serde(rename = "yes_always")] + YesAlways, + #[serde(rename = "no")] + No, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Exit plan mode action +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum ExitPlanModeAction { + #[serde(rename = "exit_only")] + ExitOnly, + #[serde(rename = "interactive")] + Interactive, + #[serde(rename = "autopilot")] + Autopilot, + #[serde(rename = "autopilot_fleet")] + AutopilotFleet, /// Unknown variant for forward compatibility. #[default] #[serde(other)] Unknown, } -/// New connection status: connected, failed, needs-auth, pending, disabled, or not_configured +/// Source location type (e.g., project, personal-copilot, plugin, builtin) +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum SkillSource { + #[serde(rename = "project")] + Project, + #[serde(rename = "inherited")] + Inherited, + #[serde(rename = "personal-copilot")] + PersonalCopilot, + #[serde(rename = "personal-agents")] + PersonalAgents, + #[serde(rename = "plugin")] + Plugin, + #[serde(rename = "custom")] + Custom, + #[serde(rename = "builtin")] + Builtin, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Configuration source: user, workspace, plugin, or builtin +#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] +pub enum McpServerSource { + #[serde(rename = "user")] + User, + #[serde(rename = "workspace")] + Workspace, + #[serde(rename = "plugin")] + Plugin, + #[serde(rename = "builtin")] + Builtin, + /// Unknown variant for forward compatibility. + #[default] + #[serde(other)] + Unknown, +} + +/// Connection status: connected, failed, needs-auth, pending, disabled, or not_configured #[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)] -pub enum McpServerStatusChangedStatus { +pub enum McpServerStatus { #[serde(rename = "connected")] Connected, #[serde(rename = "failed")] diff --git a/rust/src/lib.rs b/rust/src/lib.rs index af30b4191..6585676ec 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1062,6 +1062,7 @@ impl Client { if let Some(cfg) = session_fs_config { let session_fs_start = Instant::now(); let request = crate::generated::api_types::SessionFsSetProviderRequest { + capabilities: None, conventions: cfg.conventions.into_wire(), initial_cwd: cfg.initial_cwd, session_state_path: cfg.session_state_path, diff --git a/rust/src/session_fs.rs b/rust/src/session_fs.rs index e675760a1..8474235c7 100644 --- a/rust/src/session_fs.rs +++ b/rust/src/session_fs.rs @@ -9,10 +9,9 @@ //! # Concurrency //! //! Each inbound `sessionFs.*` request is dispatched on its own spawned task, -//! matching Node's behavior. Provider implementations MUST be safe for -//! concurrent invocation across distinct paths. Use internal synchronization -//! (e.g. [`tokio::sync::Mutex`] keyed by path) if your backing store needs -//! ordering. +//! so provider implementations MUST be safe for concurrent invocation across +//! distinct paths. Use internal synchronization (e.g. [`tokio::sync::Mutex`] +//! keyed by path) if your backing store needs ordering. //! //! # Errors //! @@ -41,12 +40,15 @@ //! } //! ``` +use std::collections::HashMap; + use async_trait::async_trait; use crate::generated::api_types::{ SessionFsError, SessionFsErrorCode, SessionFsReaddirWithTypesEntry, SessionFsReaddirWithTypesEntryType, SessionFsSetProviderConventions, SessionFsStatResult, }; +pub use crate::generated::api_types::{SessionFsSqliteQueryResult, SessionFsSqliteQueryType}; /// Configuration for a custom session filesystem provider. /// @@ -344,6 +346,24 @@ pub trait SessionFsProvider: Send + Sync + 'static { let _ = (src, dest); Err(FsError::Other("rename not supported".to_string())) } + + /// Execute a SQLite query against the provider's per-session database. + async fn sqlite_query( + &self, + session_id: &str, + query: &str, + query_type: SessionFsSqliteQueryType, + params: Option<&HashMap>, + ) -> Result { + let _ = (session_id, query, query_type, params); + Err(FsError::Other("sqlite_query not supported".to_string())) + } + + /// Check whether the provider has a SQLite database for the session. + async fn sqlite_exists(&self, session_id: &str) -> Result { + let _ = session_id; + Err(FsError::Other("sqlite_exists not supported".to_string())) + } } #[cfg(test)] diff --git a/rust/src/session_fs_dispatch.rs b/rust/src/session_fs_dispatch.rs index 7b2ae49fd..3810d978f 100644 --- a/rust/src/session_fs_dispatch.rs +++ b/rust/src/session_fs_dispatch.rs @@ -16,9 +16,11 @@ use crate::generated::api_types::{ SessionFsMkdirRequest, SessionFsReadFileRequest, SessionFsReadFileResult, SessionFsReaddirRequest, SessionFsReaddirResult, SessionFsReaddirWithTypesRequest, SessionFsReaddirWithTypesResult, SessionFsRenameRequest, SessionFsRmRequest, - SessionFsStatRequest, SessionFsStatResult, SessionFsWriteFileRequest, + SessionFsSqliteExistsParams, SessionFsSqliteExistsResult, SessionFsSqliteQueryRequest, + SessionFsSqliteQueryResult, SessionFsStatRequest, SessionFsStatResult, + SessionFsWriteFileRequest, }; -use crate::session_fs::{FsError, SessionFsProvider}; +use crate::session_fs::SessionFsProvider; use crate::{Client, JsonRpcRequest, JsonRpcResponse, error_codes}; /// Helper: serialize a typed result, send the response. @@ -146,7 +148,6 @@ pub(crate) async fn exists( } }; let id = request.id; - // Match Node's `createSessionFsAdapter`: errors collapse to `exists: false`. let exists_value = provider.exists(¶ms.path).await.unwrap_or(false); respond( client, @@ -302,6 +303,61 @@ pub(crate) async fn rename( } } +pub(crate) async fn sqlite_query( + client: &Client, + provider: &Arc, + request: JsonRpcRequest, +) { + let params: SessionFsSqliteQueryRequest = match parse_params(&request) { + Some(p) => p, + None => { + send_error(client, request.id, "invalid sessionFs.sqliteQuery params").await; + return; + } + }; + let id = request.id; + let sqlite_params = (!params.params.is_empty()).then_some(¶ms.params); + let result = match provider + .sqlite_query( + params.session_id.as_ref(), + ¶ms.query, + params.query_type, + sqlite_params, + ) + .await + { + Ok(result) => result, + Err(e) => SessionFsSqliteQueryResult { + columns: Vec::new(), + error: Some(e.into_wire()), + last_insert_rowid: None, + rows: Vec::new(), + rows_affected: 0, + }, + }; + respond(client, id, result).await; +} + +pub(crate) async fn sqlite_exists( + client: &Client, + provider: &Arc, + request: JsonRpcRequest, +) { + let params: SessionFsSqliteExistsParams = match parse_params(&request) { + Some(p) => p, + None => { + send_error(client, request.id, "invalid sessionFs.sqliteExists params").await; + return; + } + }; + let id = request.id; + let result = match provider.sqlite_exists(params.session_id.as_ref()).await { + Ok(exists) => SessionFsSqliteExistsResult { exists }, + Err(_) => SessionFsSqliteExistsResult { exists: false }, + }; + respond(client, id, result).await; +} + /// Dispatch a `sessionFs.*` request to the appropriate handler. Returns /// `true` if the request was a session-fs method (whether or not a provider /// was registered), `false` otherwise (caller should continue matching). @@ -338,6 +394,8 @@ pub(crate) async fn dispatch( "sessionFs.readdirWithTypes" => readdir_with_types(client, &provider, request).await, "sessionFs.rm" => rm(client, &provider, request).await, "sessionFs.rename" => rename(client, &provider, request).await, + "sessionFs.sqliteQuery" => sqlite_query(client, &provider, request).await, + "sessionFs.sqliteExists" => sqlite_exists(client, &provider, request).await, _ => { warn!(method = %method, "unknown sessionFs.* method"); send_error(client, request.id, "unknown sessionFs method").await; @@ -345,7 +403,3 @@ pub(crate) async fn dispatch( } true } - -// FsError is used through `into_wire()` calls above. -#[allow(dead_code)] -fn _ensure_fs_error_used(_e: FsError) {} diff --git a/rust/src/types.rs b/rust/src/types.rs index e6b49c130..0f242445e 100644 --- a/rust/src/types.rs +++ b/rust/src/types.rs @@ -16,7 +16,7 @@ use crate::handler::SessionHandler; use crate::hooks::SessionHooks; pub use crate::session_fs::{ DirEntry, DirEntryKind, FileInfo, FsError, SessionFsConfig, SessionFsConventions, - SessionFsProvider, + SessionFsProvider, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, }; pub use crate::trace_context::{TraceContext, TraceContextProvider}; use crate::transforms::SystemMessageTransform; diff --git a/rust/tests/e2e/mode_handlers.rs b/rust/tests/e2e/mode_handlers.rs index 53f7be255..1c997fccf 100644 --- a/rust/tests/e2e/mode_handlers.rs +++ b/rust/tests/e2e/mode_handlers.rs @@ -2,10 +2,13 @@ use std::sync::Arc; use async_trait::async_trait; use github_copilot_sdk::generated::session_events::{ - AutoModeSwitchCompletedData, AutoModeSwitchRequestedData, ExitPlanModeCompletedData, - ExitPlanModeRequestedData, SessionEventType, SessionModelChangeData, + AutoModeSwitchCompletedData, AutoModeSwitchRequestedData, + AutoModeSwitchResponse as EventAutoModeSwitchResponse, ExitPlanModeAction, + ExitPlanModeCompletedData, ExitPlanModeRequestedData, SessionEventType, SessionModelChangeData, +}; +use github_copilot_sdk::handler::{ + AutoModeSwitchResponse as HandlerAutoModeSwitchResponse, ExitPlanModeResult, SessionHandler, }; -use github_copilot_sdk::handler::{AutoModeSwitchResponse, ExitPlanModeResult, SessionHandler}; use github_copilot_sdk::{ExitPlanModeData, SessionConfig, SessionId}; use serde_json::json; use tokio::sync::mpsc; @@ -53,11 +56,11 @@ impl SessionHandler for AutoModeHandler { session_id: SessionId, error_code: Option, retry_after_seconds: Option, - ) -> AutoModeSwitchResponse { + ) -> HandlerAutoModeSwitchResponse { let _ = self .requests .send((session_id, error_code, retry_after_seconds)); - AutoModeSwitchResponse::Yes + HandlerAutoModeSwitchResponse::Yes } } @@ -102,7 +105,8 @@ async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { .typed_data::() .is_some_and(|data| { data.approved == Some(true) - && data.selected_action.as_deref() == Some("interactive") + && data.selected_action + == Some(ExitPlanModeAction::Interactive) }) }, )); @@ -144,10 +148,17 @@ async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { .typed_data::() .expect("typed requested event"); assert_eq!(requested_data.summary, request.summary); - assert_eq!(requested_data.actions, request.actions); + assert_eq!( + requested_data.actions, + [ + ExitPlanModeAction::Interactive, + ExitPlanModeAction::Autopilot, + ExitPlanModeAction::ExitOnly, + ] + ); assert_eq!( requested_data.recommended_action, - request.recommended_action + ExitPlanModeAction::Interactive ); let completed = completed_event.await.expect("completed task"); @@ -156,8 +167,8 @@ async fn should_invoke_exit_plan_mode_handler_when_model_uses_tool() { .expect("typed completed event"); assert_eq!(completed_data.approved, Some(true)); assert_eq!( - completed_data.selected_action.as_deref(), - Some("interactive") + completed_data.selected_action, + Some(ExitPlanModeAction::Interactive) ); assert_eq!( completed_data.feedback.as_deref(), @@ -215,7 +226,9 @@ async fn should_invoke_auto_mode_switch_handler_when_rate_limited() { event.parsed_type() == SessionEventType::AutoModeSwitchCompleted && event .typed_data::() - .is_some_and(|data| data.response == "yes") + .is_some_and(|data| { + data.response == EventAutoModeSwitchResponse::Yes + }) }, )); let model_change_event = @@ -258,7 +271,7 @@ async fn should_invoke_auto_mode_switch_handler_when_rate_limited() { let completed_data = completed .typed_data::() .expect("typed completed event"); - assert_eq!(completed_data.response, "yes"); + assert_eq!(completed_data.response, EventAutoModeSwitchResponse::Yes); let model_change = model_change_event.await.expect("model change task"); let model_change_data = model_change diff --git a/rust/tests/e2e/rpc_additional_edge_cases.rs b/rust/tests/e2e/rpc_additional_edge_cases.rs index a85da53f0..c56b39844 100644 --- a/rust/tests/e2e/rpc_additional_edge_cases.rs +++ b/rust/tests/e2e/rpc_additional_edge_cases.rs @@ -1,6 +1,7 @@ +use github_copilot_sdk::generated::SessionMode; use github_copilot_sdk::generated::api_types::{ ModeSetRequest, NameSetRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, - SessionMode, ShellExecRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, + ShellExecRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, }; use super::support::{wait_for_condition, with_e2e_context}; diff --git a/rust/tests/e2e/rpc_event_side_effects.rs b/rust/tests/e2e/rpc_event_side_effects.rs index 1c39dc317..e68939f98 100644 --- a/rust/tests/e2e/rpc_event_side_effects.rs +++ b/rust/tests/e2e/rpc_event_side_effects.rs @@ -1,5 +1,6 @@ +use github_copilot_sdk::generated::SessionMode; use github_copilot_sdk::generated::api_types::{ - HistoryTruncateRequest, ModeSetRequest, NameSetRequest, PlanUpdateRequest, SessionMode, + HistoryTruncateRequest, ModeSetRequest, NameSetRequest, PlanUpdateRequest, WorkspacesCreateFileRequest, }; use github_copilot_sdk::generated::session_events::{ @@ -30,7 +31,8 @@ async fn should_emit_mode_changed_event_when_mode_set() { let data = event .typed_data::() .expect("mode changed data"); - data.previous_mode == "interactive" && data.new_mode == "plan" + data.previous_mode == SessionMode::Interactive + && data.new_mode == SessionMode::Plan }); session .rpc() diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs index 83c527be7..e246e6a03 100644 --- a/rust/tests/e2e/rpc_session_state.rs +++ b/rust/tests/e2e/rpc_session_state.rs @@ -1,7 +1,8 @@ +use github_copilot_sdk::generated::SessionMode; use github_copilot_sdk::generated::api_types::{ HistoryTruncateRequest, McpOauthLoginRequest, ModeSetRequest, ModelSwitchToRequest, - NameSetRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionMode, - SessionsForkRequest, WorkspacesCreateFileRequest, WorkspacesReadFileRequest, + NameSetRequest, PermissionsSetApproveAllRequest, PlanUpdateRequest, SessionsForkRequest, + WorkspacesCreateFileRequest, WorkspacesReadFileRequest, }; use github_copilot_sdk::generated::session_events::{ AssistantMessageData, SessionEventType, SessionTitleChangedData, diff --git a/rust/tests/e2e/session_fs.rs b/rust/tests/e2e/session_fs.rs index f069f6ffe..217e3e883 100644 --- a/rust/tests/e2e/session_fs.rs +++ b/rust/tests/e2e/session_fs.rs @@ -5,7 +5,7 @@ use async_trait::async_trait; use github_copilot_sdk::generated::api_types::PlanUpdateRequest; use github_copilot_sdk::{ Client, DirEntry, DirEntryKind, FileInfo, FsError, SessionConfig, SessionFsConfig, - SessionFsConventions, SessionFsProvider, + SessionFsConventions, SessionFsProvider, SessionFsSqliteQueryResult, SessionFsSqliteQueryType, }; use super::support::{assistant_message_content, wait_for_condition, with_e2e_context}; @@ -206,6 +206,26 @@ async fn should_map_all_sessionfs_handler_operations() { provider.stat("/workspace/nested/missing.txt").await, Err(FsError::NotFound(_)) )); + let sqlite_params = + std::collections::HashMap::from([("answer".to_string(), serde_json::Value::from(42))]); + let sqlite_result = provider + .sqlite_query( + "handler-session", + "select :answer as answer", + SessionFsSqliteQueryType::Query, + Some(&sqlite_params), + ) + .await + .expect("sqlite query"); + assert_eq!(sqlite_result.columns[3], "answer"); + assert_eq!(sqlite_result.rows[0]["answer"], 42); + assert_eq!(sqlite_result.rows_affected, 0); + assert!( + provider + .sqlite_exists("handler-session") + .await + .expect("sqlite exists") + ); let _ = std::fs::remove_dir_all(root); } @@ -602,6 +622,52 @@ impl SessionFsProvider for TestSessionFsProvider { } std::fs::rename(src, dest).map_err(FsError::from) } + + async fn sqlite_query( + &self, + session_id: &str, + query: &str, + query_type: SessionFsSqliteQueryType, + params: Option<&std::collections::HashMap>, + ) -> Result { + let mut row = std::collections::HashMap::new(); + row.insert("sessionId".to_string(), session_id.to_string().into()); + row.insert("query".to_string(), query.to_string().into()); + row.insert( + "queryType".to_string(), + match query_type { + SessionFsSqliteQueryType::Exec => "exec", + SessionFsSqliteQueryType::Query => "query", + SessionFsSqliteQueryType::Run => "run", + SessionFsSqliteQueryType::Unknown => "unknown", + } + .into(), + ); + row.insert( + "answer".to_string(), + params + .and_then(|params| params.get("answer")) + .cloned() + .unwrap_or(serde_json::Value::Null), + ); + + Ok(SessionFsSqliteQueryResult { + columns: vec![ + "sessionId".to_string(), + "query".to_string(), + "queryType".to_string(), + "answer".to_string(), + ], + rows: vec![row], + rows_affected: 0, + last_insert_rowid: None, + error: None, + }) + } + + async fn sqlite_exists(&self, session_id: &str) -> Result { + Ok(session_id == self.session_id) + } } #[derive(Clone)] diff --git a/rust/tests/session_test.rs b/rust/tests/session_test.rs index 81ddf54f5..3a60f4663 100644 --- a/rust/tests/session_test.rs +++ b/rust/tests/session_test.rs @@ -2912,6 +2912,7 @@ async fn command_execute_handler_error_propagates_to_ack() { use github_copilot_sdk::session_fs::{ DirEntry, DirEntryKind, FileInfo, FsError, SessionFsConventions, SessionFsProvider, + SessionFsSqliteQueryResult, SessionFsSqliteQueryType, }; struct RecordingFsProvider { @@ -2983,6 +2984,59 @@ impl SessionFsProvider for RecordingFsProvider { } Ok(()) } + + async fn sqlite_query( + &self, + session_id: &str, + query: &str, + query_type: SessionFsSqliteQueryType, + params: Option<&std::collections::HashMap>, + ) -> Result { + let mut row = std::collections::HashMap::new(); + row.insert( + "sessionId".to_string(), + serde_json::Value::String(session_id.to_string()), + ); + row.insert( + "query".to_string(), + serde_json::Value::String(query.to_string()), + ); + row.insert( + "queryType".to_string(), + serde_json::Value::String( + match query_type { + SessionFsSqliteQueryType::Exec => "exec", + SessionFsSqliteQueryType::Query => "query", + SessionFsSqliteQueryType::Run => "run", + SessionFsSqliteQueryType::Unknown => "unknown", + } + .to_string(), + ), + ); + row.insert( + "answer".to_string(), + params + .and_then(|params| params.get("answer")) + .cloned() + .unwrap_or(serde_json::Value::Null), + ); + Ok(SessionFsSqliteQueryResult { + columns: vec![ + "sessionId".to_string(), + "query".to_string(), + "queryType".to_string(), + "answer".to_string(), + ], + rows: vec![row], + rows_affected: 0, + last_insert_rowid: None, + error: None, + }) + } + + async fn sqlite_exists(&self, session_id: &str) -> Result { + Ok(!session_id.is_empty()) + } } async fn create_session_pair_with_fs_provider( @@ -3102,6 +3156,121 @@ async fn session_fs_maps_other_to_unknown() { ); } +#[tokio::test] +async fn session_fs_dispatches_sqlite_query_to_provider() { + let provider = Arc::new(RecordingFsProvider::new()); + let (_session, mut server) = + create_session_pair_with_fs_provider(Arc::new(NoopHandler), provider).await; + + server + .send_request( + 9, + "sessionFs.sqliteQuery", + serde_json::json!({ + "sessionId": server.session_id, + "query": "select :answer as answer", + "queryType": "query", + "params": { "answer": 42 }, + }), + ) + .await; + + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 9); + assert_eq!(response["result"]["columns"][3], "answer"); + assert_eq!( + response["result"]["rows"][0]["query"], + "select :answer as answer" + ); + assert_eq!( + response["result"]["rows"][0]["sessionId"], + server.session_id.to_string() + ); + assert_eq!(response["result"]["rows"][0]["queryType"], "query"); + assert_eq!(response["result"]["rows"][0]["answer"], 42); + assert_eq!(response["result"]["rowsAffected"], 0); + assert!(response["result"].get("error").is_none() || response["result"]["error"].is_null()); +} + +#[tokio::test] +async fn session_fs_dispatches_sqlite_exists_to_provider() { + let provider = Arc::new(RecordingFsProvider::new()); + let (_session, mut server) = + create_session_pair_with_fs_provider(Arc::new(NoopHandler), provider).await; + + server + .send_request( + 13, + "sessionFs.sqliteExists", + serde_json::json!({ "sessionId": server.session_id }), + ) + .await; + + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 13); + assert_eq!(response["result"]["exists"], true); +} + +#[tokio::test] +async fn session_fs_maps_sqlite_errors_to_results() { + struct AlwaysFails; + #[async_trait] + impl SessionFsProvider for AlwaysFails { + async fn sqlite_query( + &self, + _session_id: &str, + _query: &str, + _query_type: SessionFsSqliteQueryType, + _params: Option<&std::collections::HashMap>, + ) -> Result { + Err(FsError::Other("sqlite unavailable".to_string())) + } + + async fn sqlite_exists(&self, _session_id: &str) -> Result { + Err(FsError::Other("sqlite unavailable".to_string())) + } + } + + let (_session, mut server) = + create_session_pair_with_fs_provider(Arc::new(NoopHandler), Arc::new(AlwaysFails)).await; + + server + .send_request( + 14, + "sessionFs.sqliteQuery", + serde_json::json!({ + "sessionId": server.session_id, + "query": "select 1", + "queryType": "query", + }), + ) + .await; + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 14); + assert_eq!(response["result"]["columns"].as_array().unwrap().len(), 0); + assert_eq!(response["result"]["rows"].as_array().unwrap().len(), 0); + assert_eq!(response["result"]["rowsAffected"], 0); + let error = &response["result"]["error"]; + assert_eq!(error["code"], "UNKNOWN"); + assert!( + error["message"] + .as_str() + .unwrap() + .contains("sqlite unavailable") + ); + + server + .send_request( + 15, + "sessionFs.sqliteExists", + serde_json::json!({ "sessionId": server.session_id }), + ) + .await; + let response = timeout(TIMEOUT, server.read_response()).await.unwrap(); + assert_eq!(response["id"], 15); + assert_eq!(response["result"]["exists"], false); +} + #[tokio::test] async fn session_fs_dispatches_write_file_with_mode() { let provider = Arc::new(RecordingFsProvider::new()); diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts index 050a21d14..9d5eb5e46 100644 --- a/scripts/codegen/csharp.ts +++ b/scripts/codegen/csharp.ts @@ -251,7 +251,7 @@ function isNonNullableCSharpValueType(typeName: string): boolean { "long", "DateTimeOffset", "TimeSpan", - ].includes(typeName) || generatedEnums.has(typeName) || emittedRpcEnumResultTypes.has(typeName); + ].includes(typeName) || generatedEnums.has(typeName) || emittedRpcEnumResultTypes.has(typeName) || externalRpcValueTypes.has(typeName); } function requiresArgumentNullCheck(typeName: string, isRequired: boolean): boolean { @@ -1351,6 +1351,7 @@ let experimentalRpcTypes = new Set(); let nonExperimentalRpcTypes = new Set(); let rpcKnownTypes = new Map(); let rpcEnumOutput: string[] = []; +let externalRpcValueTypes = new Set(); /** Schema definitions available during RPC generation (for $ref resolution). */ let rpcDefinitions: DefinitionCollections = { definitions: {}, $defs: {} }; @@ -2156,7 +2157,8 @@ function emitClientSessionApiRegistration(clientSchema: Record, function generateRpcCode( schema: ApiSchema, - externalJsonSerializableRefs: Map> = new Map() + externalJsonSerializableRefs: Map> = new Map(), + externalValueTypes: Set = new Set() ): string { emittedRpcClassSchemas.clear(); emittedRpcEnumResultTypes.clear(); @@ -2165,6 +2167,7 @@ function generateRpcCode( rpcKnownTypes.clear(); rpcEnumOutput = []; generatedEnums.clear(); // Clear shared enum deduplication map + externalRpcValueTypes = new Set([...externalValueTypes].map(typeToClassName)); rpcDefinitions = collectDefinitionCollections(schema as Record); const allMethods = [ ...collectRpcMethods(schema.server || {}), @@ -2264,6 +2267,7 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO schema = rewriteSharedDefinitionReferences(schema, sharedDefinitions, "session-events.schema.json"); } const externalJsonSerializableRefs = new Map>(); + const externalValueTypes = new Set(); if (sessionEventsSchema) { const sessionEventsCode = generateSessionEventsCode(sessionEventsSchema); const externalRefs = collectExternalSchemaRefNames(schema); @@ -2280,6 +2284,10 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO if (declarationPattern.test(sessionEventsCode)) { emittedDefinitions.add(name); } + const valueTypeDeclarationPattern = new RegExp(`\\bpublic\\s+(?:(?:readonly)\\s+)?struct\\s+${typeName}\\b`); + if (valueTypeDeclarationPattern.test(sessionEventsCode)) { + externalValueTypes.add(name); + } } externalJsonSerializableRefs.set( "session-events.schema.json", @@ -2287,7 +2295,7 @@ export async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSO ); } } - const code = generateRpcCode(schema, externalJsonSerializableRefs); + const code = generateRpcCode(schema, externalJsonSerializableRefs, externalValueTypes); const outPath = await writeGeneratedFile("dotnet/src/Generated/Rpc.cs", code); console.log(` ✓ ${outPath}`); await formatCSharpFile(outPath); diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts index 02c59e58f..dae1b875e 100644 --- a/scripts/codegen/python.ts +++ b/scripts/codegen/python.ts @@ -126,29 +126,78 @@ function placeholderToQuicktypeIdentifier(placeholder: string): string { .join(""); } -function postProcessExternalRefsForPython(code: string, placeholderToReal: Map): string { +function placeholderToQuicktypeIdentifiers(placeholder: string): string[] { + const basic = placeholderToQuicktypeIdentifier(placeholder); + return [...new Set([basic, basic.replace(/Mcp/g, "MCP")])]; +} + +function postProcessExternalRefsForPython( + code: string, + placeholderToReal: Map, + externalEnumNames: Set = new Set() +): string { for (const [placeholder, realName] of placeholderToReal) { - const quicktypeName = placeholderToQuicktypeIdentifier(placeholder); - code = code.replace( - new RegExp( - `(?:^|\\n)@dataclass\\r?\\nclass ${quicktypeName}\\b[\\s\\S]*?(?=\\n@dataclass\\b|\\nclass\\s+\\w|\\ndef\\s+\\w|$)`, - "g" - ), - "\n" - ); - code = code.replace( - new RegExp( - `(?:^|\\n)class ${quicktypeName}\\w*\\(Enum\\):[\\s\\S]*?(?=\\nclass\\s+\\w|\\n@dataclass\\b|\\ndef\\s+\\w|$)`, - "g" - ), - "\n" - ); - code = code.replace(new RegExp(`\\b${quicktypeName}\\b`, "g"), realName); + for (const quicktypeName of placeholderToQuicktypeIdentifiers(placeholder)) { + code = code.replace( + new RegExp( + `(?:^|\\n)@dataclass\\r?\\nclass ${quicktypeName}\\b[\\s\\S]*?(?=\\n@dataclass\\b|\\nclass\\s+\\w|\\ndef\\s+\\w|$)`, + "g" + ), + "\n" + ); + code = code.replace( + new RegExp( + `(?:^|\\n)class ${quicktypeName}\\w*\\(Enum\\):[\\s\\S]*?(?=\\nclass\\s+\\w|\\n@dataclass\\b|\\ndef\\s+\\w|$)`, + "g" + ), + "\n" + ); + code = code.replace(new RegExp(`\\b${quicktypeName}\\b`, "g"), realName); + } + if (externalEnumNames.has(realName)) { + code = code.replace(new RegExp(`\\b${realName}\\.from_dict\\b`, "g"), realName); + code = code.replace( + new RegExp(`to_class\\(${realName},\\s*([^)]+)\\)`, "g"), + `to_enum(${realName}, $1)` + ); + } } return code.replace(/\n{3,}/g, "\n\n"); } +function collectPythonExternalEnumNames( + schema: JSONSchema7 | undefined, + placeholderToReal: Map +): Set { + const enumNames = new Set(); + if (!schema) return enumNames; + + const definitions = collectDefinitionCollections(schema as Record); + for (const realName of placeholderToReal.values()) { + const definition = definitions.definitions[realName] ?? definitions.$defs[realName]; + const resolved = definition ? resolveSchema(definition, definitions) ?? definition : undefined; + if ( + resolved?.enum && + Array.isArray(resolved.enum) && + resolved.enum.every((value) => typeof value === "string") + ) { + enumNames.add(realName); + } + } + + return enumNames; +} + +function preservePythonRpcStringDateFields(definitions: Record): void { + const quotaSnapshot = definitions.AccountQuotaSnapshot; + const resetDate = quotaSnapshot?.properties?.resetDate as JSONSchema7 | undefined; + if (resetDate?.type === "string" && resetDate.format === "date-time") { + // Keep the existing Python API shape: AccountQuotaSnapshot.reset_date is an ISO string. + delete resetDate.format; + } +} + function collectExternalUnionAliasesForPython( definitions: Record, placeholderToReal: Map @@ -2083,6 +2132,7 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema } const allDefinitions = combinedSchema.definitions! as Record; + preservePythonRpcStringDateFields(allDefinitions); const allDefinitionCollections: DefinitionCollections = { definitions: { ...(combinedSchema.$defs ?? {}), ...allDefinitions }, $defs: { ...allDefinitions, ...(combinedSchema.$defs ?? {}) }, @@ -2101,6 +2151,7 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema required: Object.keys(allDefinitions), }; const externalRefs = rewriteExternalRefsForPython(singleSchema as JSONSchema7 & { definitions?: Record }); + const externalEnumNames = collectPythonExternalEnumNames(sessionEventsSchema, externalRefs.placeholderNames); const externalUnionAliases = collectExternalUnionAliasesForPython( singleSchema.definitions as Record, externalRefs.placeholderNames @@ -2128,7 +2179,8 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema const knownDefNames = new Set(Object.keys(allDefinitions).map((n) => n.toLowerCase())); typesCode = collapsePlaceholderPythonDataclasses(typesCode, knownDefNames); typesCode = postProcessExternalUnionAliasesForPython(typesCode, externalUnionAliases); - typesCode = postProcessExternalRefsForPython(typesCode, externalRefs.placeholderNames); + typesCode = postProcessExternalRefsForPython(typesCode, externalRefs.placeholderNames, externalEnumNames); + typesCode = modernizePython(typesCode); // Fix quicktype's Enum-suffix renaming: quicktype sometimes renames "Xyz" to // "XyzEnum" to avoid internal collisions. Strip the suffix to match our schema @@ -2235,6 +2287,15 @@ async function generateRpc(schemaPath?: string, sessionEventsSchema?: JSONSchema } } } + const compatibilityTypeAliases = new Map([ + ["TaskInfoExecutionMode", "TaskExecutionMode"], + ["TaskInfoStatus", "TaskStatus"], + ]); + for (const [aliasName, targetName] of compatibilityTypeAliases) { + if (actualTypeNames.has(targetName.toLowerCase()) && !actualTypeNames.has(aliasName.toLowerCase())) { + publicTypeAliases.set(aliasName, actualTypeNames.get(targetName.toLowerCase()) ?? targetName); + } + } const resolveType = (name: string): string => actualTypeNames.get(name.toLowerCase()) ?? definitionAliases.get(name.toLowerCase()) ?? name; diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts index 9561d9cc0..3898ee7f2 100644 --- a/scripts/codegen/rust.ts +++ b/scripts/codegen/rust.ts @@ -1285,6 +1285,43 @@ function generateApiTypesCode(apiSchema: ApiSchema): string { } } const allMethods = methodEntries.map(({ method }) => method); + const inlineMethodParamSchemas = new Map(); + const sortedNames = (names: Iterable | undefined): string[] => + [...(names ?? [])].sort(); + const schemaPropertyNames = (schema: JSONSchema7): string[] => + sortedNames(Object.keys(schema.properties ?? {})); + const shouldPreferMethodParamSchema = ( + typeName: string, + paramsSchema: JSONSchema7, + ): boolean => { + const definition = definitions[typeName]; + if (typeof definition !== "object" || definition === null) return false; + const definitionSchema = asGeneratedObjectSchema( + definition as JSONSchema7, + defCollections, + ); + if (!definitionSchema) return false; + + return ( + JSON.stringify(schemaPropertyNames(paramsSchema)) !== + JSON.stringify(schemaPropertyNames(definitionSchema)) || + JSON.stringify(sortedNames(paramsSchema.required)) !== + JSON.stringify(sortedNames(definitionSchema.required)) + ); + }; + for (const { method, isSession } of methodEntries) { + const params = method.params as (JSONSchema7 & { $ref?: string }) | undefined; + if (!params || typeof params.$ref === "string") continue; + const paramsSchema = getMethodParamsObjectSchema( + method, + defCollections, + isSession, + ); + const paramsName = rustParamsTypeName(method, defCollections); + if (paramsSchema && shouldPreferMethodParamSchema(paramsName, paramsSchema)) { + inlineMethodParamSchemas.set(paramsName, paramsSchema); + } + } for (const name of collectExperimentalOnlyRpcReferencedDefinitionNames(allMethods, defCollections)) { ctx.experimentalTypeNames.add(toPascalCase(name)); } @@ -1316,7 +1353,7 @@ function generateApiTypesCode(apiSchema: ApiSchema): string { // Generate shared definitions (structs & enums) for (const [name, def] of Object.entries(definitions)) { if (typeof def !== "object" || def === null) continue; - const schema = def as JSONSchema7; + const schema = inlineMethodParamSchemas.get(name) ?? (def as JSONSchema7); if (schema.enum && Array.isArray(schema.enum)) { emitRustStringEnum( @@ -1423,9 +1460,6 @@ function generateApiTypesCode(apiSchema: ApiSchema): string { )) { out.push(`use ${module}::{${[...typeNames].sort().join(", ")}};`); } - if (externalImports.size > 0) { - out.push(""); - } out.push("use crate::types::{RequestId, SessionId};"); out.push(""); @@ -1791,6 +1825,43 @@ function generateRpcCode(apiSchema: ApiSchema): string { out.push("#![allow(clippy::too_many_arguments)]"); out.push(""); out.push("use super::api_types::{rpc_methods, *};"); + const externalTypeRefs = new Map>(); + const recordExternalTypeRef = (ref: string | undefined): void => { + if (!ref) return; + const externalRef = parseExternalSchemaRef(ref); + if (!externalRef) return; + let typeNames = externalTypeRefs.get(externalRef.schemaFile); + if (!typeNames) { + typeNames = new Set(); + externalTypeRefs.set(externalRef.schemaFile, typeNames); + } + typeNames.add(externalRef.definitionName); + }; + for (const method of [...serverMethods, ...sessionMethods]) { + recordExternalTypeRef(method.params?.$ref); + recordExternalTypeRef(method.result?.$ref); + recordExternalTypeRef(getNullableInner(method.result)?.$ref); + } + const externalImports = new Map>(); + for (const [schemaFile, typeNames] of externalTypeRefs) { + const defaultModule = EXTERNAL_SCHEMA_RUST_MODULE[schemaFile]; + const typeModules = EXTERNAL_SCHEMA_RUST_TYPE_MODULE[schemaFile] ?? {}; + for (const typeName of typeNames) { + const module = typeModules[typeName] ?? defaultModule; + if (!module) continue; + let names = externalImports.get(module); + if (!names) { + names = new Set(); + externalImports.set(module, names); + } + names.add(typeName); + } + } + for (const [module, typeNames] of [...externalImports].sort(([left], [right]) => + left.localeCompare(right), + )) { + out.push(`use ${module}::{${[...typeNames].sort().join(", ")}};`); + } out.push("use crate::session::Session;"); out.push("use crate::{Client, Error};"); out.push(""); diff --git a/scripts/codegen/typescript.ts b/scripts/codegen/typescript.ts index 0f2366909..78c232ed5 100644 --- a/scripts/codegen/typescript.ts +++ b/scripts/codegen/typescript.ts @@ -497,7 +497,8 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; for (const method of rpcMethods) { const resultSchema = getMethodResultSchema(method); - if (!isVoidSchema(resultSchema) && !getNullableInner(resultSchema)) { + const resultExternalRef = method.result?.$ref ? parseExternalSchemaRef(method.result.$ref) : undefined; + if (!resultExternalRef && !isVoidSchema(resultSchema) && !getNullableInner(resultSchema)) { const resultSource = schemaSourceForNamedDefinition(method.result, resultSchema); combinedSchema.definitions![resultTypeName(method)] = withRootTitle( resultSource, @@ -513,6 +514,10 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js"; const resolvedParams = getMethodParamsSchema(method); if (method.params && hasSchemaPayload(resolvedParams)) { + const paramsExternalRef = method.params.$ref ? parseExternalSchemaRef(method.params.$ref) : undefined; + if (paramsExternalRef) { + continue; + } if (method.rpcMethod.startsWith("session.") && resolvedParams?.properties) { const filtered: JSONSchema7 = { ...resolvedParams, diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 6cc3fe72f..dad0ea1d1 100644 --- a/test/harness/package-lock.json +++ b/test/harness/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@github/copilot": "^1.0.49-1", + "@github/copilot": "^1.0.49-6", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14", @@ -464,27 +464,29 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.49-1.tgz", - "integrity": "sha512-1euPT6WXtLWnoqz1SXHdcqmktucdkfwfZn/Eo4iQ1FAjZo7awuN86rVb1feDwxY4vlSGbzNmK+GDKDgs9qZCDg==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.49-6.tgz", + "integrity": "sha512-9ptx1Vs6aJvybo7vN1gGHNPHt5JqmhIZWyurnMMFjoZh6DAq9NO+0yWBP1WL752ycFDE/kKR+OgKC64O+UsLQw==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.49-1", - "@github/copilot-darwin-x64": "1.0.49-1", - "@github/copilot-linux-arm64": "1.0.49-1", - "@github/copilot-linux-x64": "1.0.49-1", - "@github/copilot-win32-arm64": "1.0.49-1", - "@github/copilot-win32-x64": "1.0.49-1" + "@github/copilot-darwin-arm64": "1.0.49-6", + "@github/copilot-darwin-x64": "1.0.49-6", + "@github/copilot-linux-arm64": "1.0.49-6", + "@github/copilot-linux-x64": "1.0.49-6", + "@github/copilot-linuxmusl-arm64": "1.0.49-6", + "@github/copilot-linuxmusl-x64": "1.0.49-6", + "@github/copilot-win32-arm64": "1.0.49-6", + "@github/copilot-win32-x64": "1.0.49-6" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.49-1.tgz", - "integrity": "sha512-EgHdwlkYSJ+RmHAelGGpQxQe5/dgq3BlvToc0VmYEUCWO93ESEql7XBqCWYeASg3USUp8n87kf3mr2eXIECvLA==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.49-6.tgz", + "integrity": "sha512-e+0T9DIfaE5GyFnsIUQWSGhi/Ont9/iENLb43jyAsASxY+gWxqWyUHVD7kYJpunMODNjg0FNXgCEAX5YUyKOXQ==", "cpu": [ "arm64" ], @@ -499,9 +501,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.49-1.tgz", - "integrity": "sha512-YPtOW5q3vWB9Covn08jxqIdIjcCuJi/MgIlYk1ulKTINi5uK5a6NlsX2mDaGWL/svhDwDlhFEa3oUV41yOjTkg==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.49-6.tgz", + "integrity": "sha512-zgcMxEszygPvmki/A6aydLTMYyQhHQrQ5z+7BA/yGbuyghRKfe0mYG56QKRp5PsJJ01YK2Kr+G7EqgHypS5PVA==", "cpu": [ "x64" ], @@ -516,9 +518,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.49-1.tgz", - "integrity": "sha512-eEh0ec1UlWg8IdV2/3Zaxr/PAA86GclEFUcGNkwc9JceOgw5nhIdytsjCwXJUcRTzHsGrAoTS+Vad1RSvKSmYQ==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.49-6.tgz", + "integrity": "sha512-0/KlHumd38nYP/fNGVHaxSdqdRKV8cESaytTPyGq0ncfPMZzcqZhkgv6qur1XMai0zh0ZGpwxqzB3kwMrNJCoQ==", "cpu": [ "arm64" ], @@ -533,9 +535,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.49-1.tgz", - "integrity": "sha512-9+HxOVAbgCqcoyfAXyfaFxgIbAfHWCh699WuOfWViX2fjoKO3V0ZVHEergR4gVEgvnjvnmD0TZhT7+kTzqPK6A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.49-6.tgz", + "integrity": "sha512-LWfl79uh0i2/OTKikliZ3b0pnheVkEA2CYJTZUapfTSXKHQlQQIMR7HBhD6GCaOkVvHYgaH5WQVviedmS88N7g==", "cpu": [ "x64" ], @@ -549,10 +551,44 @@ "copilot-linux-x64": "copilot" } }, + "node_modules/@github/copilot-linuxmusl-arm64": { + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-arm64/-/copilot-linuxmusl-arm64-1.0.49-6.tgz", + "integrity": "sha512-O9DdXVMdNdrgucLr4gd4djbzTdH8MGitNOWqIxTbsPy9YMd/OQ9JTEDCqPuezbiVzI0emS9NyrdSBasvcVI1VQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linuxmusl-arm64": "copilot" + } + }, + "node_modules/@github/copilot-linuxmusl-x64": { + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-linuxmusl-x64/-/copilot-linuxmusl-x64-1.0.49-6.tgz", + "integrity": "sha512-Yj8FTs9JcHvXw/FS8PEK0IxWa/qf+5UWPejburofi7hwiaC4wb+pX0AzhWee4jKcJ1YbZBEyWFMvBEK6xZ0TmA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "SEE LICENSE IN LICENSE.md", + "optional": true, + "os": [ + "linux" + ], + "bin": { + "copilot-linuxmusl-x64": "copilot" + } + }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.49-1.tgz", - "integrity": "sha512-nsOz2rdk1Il3KJ24x3Hdv27MvotrKygIC/ok6acvq+xFwsYxR5Kt5bL1veBAGZVEG8K+0r2DfHi9NZHazBYK8A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.49-6.tgz", + "integrity": "sha512-u7GOWUIWRsS5IUdprXN2nWsTSTdM//m/LfqmOp1dfAxdSBOL4dF1Up3tEi8+f+HaCqIQhYQMryRux9KP4bUEnw==", "cpu": [ "arm64" ], @@ -567,9 +603,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.49-1", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.49-1.tgz", - "integrity": "sha512-RZbU3GESkfwd8UC1h5AeceVfCOfXjMA+sDKfIUyk8Pl8EukTNtNSf+WEKK1HzSxbxdbIu9DJyBL375JMwDiH4A==", + "version": "1.0.49-6", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.49-6.tgz", + "integrity": "sha512-ayxTr2+miHaguaF0QrV6a/QvoMY4wUaTaYkZ7657ONOnR6BcFV9SNtnrlpLrbiIRkP6T0QZQXhROLaQwF7vrdw==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 57082e2b9..4b00a3939 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.49-1", + "@github/copilot": "^1.0.49-6", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "@types/node-forge": "^1.3.14",