diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index 791f70d45..692d511f7 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -632,7 +632,8 @@ public async Task ResumeSessionAsync(string sessionId, ResumeSes Traceparent: traceparent, Tracestate: tracestate, ModelCapabilities: config.ModelCapabilities, - GitHubToken: config.GitHubToken); + GitHubToken: config.GitHubToken, + ContinuePendingWork: config.ContinuePendingWork); var response = await InvokeRpcAsync( connection.Rpc, "session.resume", [request], cancellationToken); @@ -1705,7 +1706,8 @@ internal record ResumeSessionRequest( string? Traceparent = null, string? Tracestate = null, ModelCapabilitiesOverride? ModelCapabilities = null, - string? GitHubToken = null); + string? GitHubToken = null, + bool? ContinuePendingWork = null); internal record ResumeSessionResponse( string SessionId, diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs index f90d836c9..9d1a76558 100644 --- a/dotnet/src/Generated/Rpc.cs +++ b/dotnet/src/Generated/Rpc.cs @@ -1676,16 +1676,16 @@ internal sealed class SessionExtensionsReloadRequest public string SessionId { get; set; } = string.Empty; } -/// RPC data type for HandleToolCall operations. -public sealed class HandleToolCallResult +/// RPC data type for HandlePendingToolCall operations. +public sealed class HandlePendingToolCallResult { /// Whether the tool call result was handled successfully. [JsonPropertyName("success")] public bool Success { get; set; } } -/// RPC data type for ToolsHandlePendingToolCall operations. -internal sealed class ToolsHandlePendingToolCallRequest +/// RPC data type for HandlePendingToolCall operations. +internal sealed class HandlePendingToolCallRequest { /// Error message if the tool call failed. [JsonPropertyName("error")] @@ -1811,6 +1811,7 @@ public sealed class PermissionRequestResult [JsonDerivedType(typeof(PermissionDecisionApproveOnce), "approve-once")] [JsonDerivedType(typeof(PermissionDecisionApproveForSession), "approve-for-session")] [JsonDerivedType(typeof(PermissionDecisionApproveForLocation), "approve-for-location")] +[JsonDerivedType(typeof(PermissionDecisionApprovePermanently), "approve-permanently")] [JsonDerivedType(typeof(PermissionDecisionReject), "reject")] [JsonDerivedType(typeof(PermissionDecisionUserNotAvailable), "user-not-available")] public partial class PermissionDecision @@ -1933,8 +1934,14 @@ public partial class PermissionDecisionApproveForSession : PermissionDecision public override string Kind => "approve-for-session"; /// The approval to add as a session-scoped rule. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("approval")] - public required PermissionDecisionApproveForSessionApproval Approval { get; set; } + public PermissionDecisionApproveForSessionApproval? Approval { get; set; } + + /// The URL domain to approve for this session. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("domain")] + public string? Domain { get; set; } } /// The approval to persist for this location. @@ -2049,6 +2056,18 @@ public partial class PermissionDecisionApproveForLocation : PermissionDecision public required string LocationKey { get; set; } } +/// The approve-permanently variant of . +public partial class PermissionDecisionApprovePermanently : PermissionDecision +{ + /// + [JsonIgnore] + public override string Kind => "approve-permanently"; + + /// The URL domain to approve permanently. + [JsonPropertyName("domain")] + public required string Domain { get; set; } +} + /// The reject variant of . public partial class PermissionDecisionReject : PermissionDecision { @@ -2293,6 +2312,15 @@ public sealed class UsageMetricsModelMetricRequests public long Count { get; set; } } +/// RPC data type for UsageMetricsModelMetricTokenDetail operations. +public sealed class UsageMetricsModelMetricTokenDetail +{ + /// Accumulated token count for this token type. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } +} + /// Token usage metrics for this model. public sealed class UsageMetricsModelMetricUsage { @@ -2329,11 +2357,29 @@ public sealed class UsageMetricsModelMetric [JsonPropertyName("requests")] public UsageMetricsModelMetricRequests Requests { get => field ??= new(); set; } + /// Token count details per type. + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + + /// Accumulated nano-AI units cost for this model. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("totalNanoAiu")] + public long? TotalNanoAiu { get; set; } + /// Token usage metrics for this model. [JsonPropertyName("usage")] public UsageMetricsModelMetricUsage Usage { get => field ??= new(); set; } } +/// RPC data type for UsageMetricsTokenDetail operations. +public sealed class UsageMetricsTokenDetail +{ + /// Accumulated token count for this token type. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("tokenCount")] + public long TokenCount { get; set; } +} + /// RPC data type for UsageGetMetrics operations. [Experimental(Diagnostics.Experimental)] public sealed class UsageGetMetricsResult @@ -2364,12 +2410,21 @@ public sealed class UsageGetMetricsResult [JsonPropertyName("sessionStartTime")] public long SessionStartTime { get; set; } + /// Session-wide per-token-type accumulated token counts. + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + /// Total time spent in model API calls (milliseconds). [Range(0, double.MaxValue)] [JsonConverter(typeof(MillisecondsTimeSpanConverter))] [JsonPropertyName("totalApiDurationMs")] public TimeSpan TotalApiDurationMs { get; set; } + /// Session-wide accumulated nano-AI units cost. + [Range((double)0, (double)long.MaxValue)] + [JsonPropertyName("totalNanoAiu")] + public long? TotalNanoAiu { get; set; } + /// Total user-initiated premium request cost across all models (may be fractional due to multipliers). [JsonPropertyName("totalPremiumRequestCost")] public double TotalPremiumRequestCost { get; set; } @@ -3898,10 +3953,10 @@ internal ToolsApi(JsonRpc rpc, string sessionId) } /// Calls "session.tools.handlePendingToolCall". - public async Task HandlePendingToolCallAsync(string requestId, object? result = null, string? error = null, CancellationToken cancellationToken = default) + public async Task HandlePendingToolCallAsync(string requestId, object? result = null, string? error = null, CancellationToken cancellationToken = default) { - var request = new ToolsHandlePendingToolCallRequest { SessionId = _sessionId, RequestId = requestId, Result = result, Error = error }; - return await CopilotClient.InvokeRpcAsync(_rpc, "session.tools.handlePendingToolCall", [request], cancellationToken); + var request = new HandlePendingToolCallRequest { SessionId = _sessionId, RequestId = requestId, Result = result, Error = error }; + return await CopilotClient.InvokeRpcAsync(_rpc, "session.tools.handlePendingToolCall", [request], cancellationToken); } } @@ -4190,7 +4245,8 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncWhen true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("continuePendingWork")] + public bool? ContinuePendingWork { get; set; } + /// Total number of persisted events in the session at the time of resume. [JsonPropertyName("eventCount")] public required double EventCount { get; set; } @@ -1214,6 +1219,11 @@ public partial class SessionResumeData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("selectedModel")] public string? SelectedModel { get; set; } + + /// True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("sessionWasActive")] + public bool? SessionWasActive { get; set; } } /// Notifies Mission Control that the session's remote steering capability has changed. @@ -1517,6 +1527,11 @@ public partial class SessionShutdownData [JsonPropertyName("systemTokens")] public double? SystemTokens { get; set; } + /// Session-wide per-token-type accumulated token counts. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + /// Tool definitions token count at shutdown. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolDefinitionsTokens")] @@ -1526,6 +1541,11 @@ public partial class SessionShutdownData [JsonPropertyName("totalApiDurationMs")] public required double TotalApiDurationMs { get; set; } + /// Session-wide accumulated nano-AI units cost. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalNanoAiu")] + public double? TotalNanoAiu { get; set; } + /// Total number of premium API requests used during the session. [JsonPropertyName("totalPremiumRequests")] public required double TotalPremiumRequests { get; set; } @@ -1748,6 +1768,11 @@ public partial class UserMessageData [JsonPropertyName("nativeDocumentPathFallbackPaths")] public string[]? NativeDocumentPathFallbackPaths { get; set; } + /// Parent agent task ID for background telemetry correlated to this user turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("parentAgentTaskId")] + public string? ParentAgentTaskId { get; set; } + /// Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user). [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("source")] @@ -1878,6 +1903,11 @@ public partial class AssistantMessageData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolRequests")] public AssistantMessageToolRequest[]? ToolRequests { get; set; } + + /// Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("turnId")] + public string? TurnId { get; set; } } /// Streaming assistant message delta for incremental response updates. @@ -2094,6 +2124,11 @@ public partial class ToolExecutionStartData /// Name of the tool being executed. [JsonPropertyName("toolName")] public required string ToolName { get; set; } + + /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("turnId")] + public string? TurnId { get; set; } } /// Streaming tool execution output for incremental result display. @@ -2166,6 +2201,11 @@ public partial class ToolExecutionCompleteData [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] [JsonPropertyName("toolTelemetry")] public IDictionary? ToolTelemetry { get; set; } + + /// Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("turnId")] + public string? TurnId { get; set; } } /// Skill invocation details including content, allowed tools, and plugin metadata. @@ -2429,7 +2469,7 @@ public partial class PermissionCompletedData /// The result of the permission request. [JsonPropertyName("result")] - public required PermissionCompletedResult Result { get; set; } + public required PermissionResult Result { get; set; } /// Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts. [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -2929,6 +2969,14 @@ public partial class ShutdownModelMetricRequests public required double Count { get; set; } } +/// Nested data type for ShutdownModelMetricTokenDetail. +public partial class ShutdownModelMetricTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public required double TokenCount { get; set; } +} + /// Token usage breakdown. /// Nested data type for ShutdownModelMetricUsage. public partial class ShutdownModelMetricUsage @@ -2962,11 +3010,29 @@ public partial class ShutdownModelMetric [JsonPropertyName("requests")] public required ShutdownModelMetricRequests Requests { get; set; } + /// Token count details per type. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("tokenDetails")] + public IDictionary? TokenDetails { get; set; } + + /// Accumulated nano-AI units cost for this model. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("totalNanoAiu")] + public double? TotalNanoAiu { get; set; } + /// Token usage breakdown. [JsonPropertyName("usage")] public required ShutdownModelMetricUsage Usage { get; set; } } +/// Nested data type for ShutdownTokenDetail. +public partial class ShutdownTokenDetail +{ + /// Accumulated token count for this token type. + [JsonPropertyName("tokenCount")] + public required double TokenCount { get; set; } +} + /// Token usage detail for a single billing category. /// Nested data type for CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail. public partial class CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail @@ -2996,7 +3062,7 @@ public partial class CompactionCompleteCompactionTokensUsedCopilotUsage [JsonPropertyName("tokenDetails")] public required CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[] TokenDetails { get; set; } - /// Total cost in nano-AIU (AI Units) for this request. + /// Total cost in nano-AI units for this request. [JsonPropertyName("totalNanoAiu")] public required double TotalNanoAiu { get; set; } } @@ -3294,7 +3360,7 @@ public partial class AssistantUsageCopilotUsage [JsonPropertyName("tokenDetails")] public required AssistantUsageCopilotUsageTokenDetail[] TokenDetails { get; set; } - /// Total cost in nano-AIU (AI Units) for this request. + /// Total cost in nano-AI units for this request. [JsonPropertyName("totalNanoAiu")] public required double TotalNanoAiu { get; set; } } @@ -4313,15 +4379,244 @@ public partial class PermissionPromptRequest } +/// The approved variant of . +public partial class PermissionResultApproved : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved"; +} + +/// The commands variant of . +public partial class UserToolSessionApprovalCommands : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "commands"; + + /// Command identifiers approved by the user. + [JsonPropertyName("commandIdentifiers")] + public required string[] CommandIdentifiers { get; set; } +} + +/// The read variant of . +public partial class UserToolSessionApprovalRead : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "read"; +} + +/// The write variant of . +public partial class UserToolSessionApprovalWrite : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "write"; +} + +/// The mcp variant of . +public partial class UserToolSessionApprovalMcp : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "mcp"; + + /// MCP server name. + [JsonPropertyName("serverName")] + public required string ServerName { get; set; } + + /// Optional MCP tool name, or null for all tools on the server. + [JsonPropertyName("toolName")] + public string? ToolName { get; set; } +} + +/// The memory variant of . +public partial class UserToolSessionApprovalMemory : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "memory"; +} + +/// The custom-tool variant of . +public partial class UserToolSessionApprovalCustomTool : UserToolSessionApproval +{ + /// + [JsonIgnore] + public override string Kind => "custom-tool"; + + /// Custom tool name. + [JsonPropertyName("toolName")] + public required string ToolName { get; set; } +} + +/// The approval to add as a session-scoped rule. +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(UserToolSessionApprovalCommands), "commands")] +[JsonDerivedType(typeof(UserToolSessionApprovalRead), "read")] +[JsonDerivedType(typeof(UserToolSessionApprovalWrite), "write")] +[JsonDerivedType(typeof(UserToolSessionApprovalMcp), "mcp")] +[JsonDerivedType(typeof(UserToolSessionApprovalMemory), "memory")] +[JsonDerivedType(typeof(UserToolSessionApprovalCustomTool), "custom-tool")] +public partial class UserToolSessionApproval +{ + /// The type discriminator. + [JsonPropertyName("kind")] + public virtual string Kind { get; set; } = string.Empty; +} + + +/// The approved-for-session variant of . +public partial class PermissionResultApprovedForSession : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-session"; + + /// The approval to add as a session-scoped rule. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } +} + +/// The approved-for-location variant of . +public partial class PermissionResultApprovedForLocation : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "approved-for-location"; + + /// The approval to persist for this location. + [JsonPropertyName("approval")] + public required UserToolSessionApproval Approval { get; set; } + + /// The location key (git root or cwd) to persist the approval to. + [JsonPropertyName("locationKey")] + public required string LocationKey { get; set; } +} + +/// The cancelled variant of . +public partial class PermissionResultCancelled : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "cancelled"; + + /// Optional explanation of why the request was cancelled. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("reason")] + public string? Reason { get; set; } +} + +/// Nested data type for PermissionRule. +public partial class PermissionRule +{ + /// Optional rule argument matched against the request. + [JsonPropertyName("argument")] + public string? Argument { get; set; } + + /// The rule kind, such as Shell or GitHubMCP. + [JsonPropertyName("kind")] + public required string Kind { get; set; } +} + +/// The denied-by-rules variant of . +public partial class PermissionResultDeniedByRules : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-rules"; + + /// Rules that denied the request. + [JsonPropertyName("rules")] + public required PermissionRule[] Rules { get; set; } +} + +/// The denied-no-approval-rule-and-could-not-request-from-user variant of . +public partial class PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-no-approval-rule-and-could-not-request-from-user"; +} + +/// The denied-interactively-by-user variant of . +public partial class PermissionResultDeniedInteractivelyByUser : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-interactively-by-user"; + + /// Optional feedback from the user explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("feedback")] + public string? Feedback { get; set; } + + /// Whether to force-reject the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("forceReject")] + public bool? ForceReject { get; set; } +} + +/// The denied-by-content-exclusion-policy variant of . +public partial class PermissionResultDeniedByContentExclusionPolicy : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-content-exclusion-policy"; + + /// Human-readable explanation of why the path was excluded. + [JsonPropertyName("message")] + public required string Message { get; set; } + + /// File path that triggered the exclusion. + [JsonPropertyName("path")] + public required string Path { get; set; } +} + +/// The denied-by-permission-request-hook variant of . +public partial class PermissionResultDeniedByPermissionRequestHook : PermissionResult +{ + /// + [JsonIgnore] + public override string Kind => "denied-by-permission-request-hook"; + + /// Whether to interrupt the current agent turn. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("interrupt")] + public bool? Interrupt { get; set; } + + /// Optional message from the hook explaining the denial. + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [JsonPropertyName("message")] + public string? Message { get; set; } +} + /// The result of the permission request. -/// Nested data type for PermissionCompletedResult. -public partial class PermissionCompletedResult +/// Polymorphic base type discriminated by kind. +[JsonPolymorphic( + TypeDiscriminatorPropertyName = "kind", + UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)] +[JsonDerivedType(typeof(PermissionResultApproved), "approved")] +[JsonDerivedType(typeof(PermissionResultApprovedForSession), "approved-for-session")] +[JsonDerivedType(typeof(PermissionResultApprovedForLocation), "approved-for-location")] +[JsonDerivedType(typeof(PermissionResultCancelled), "cancelled")] +[JsonDerivedType(typeof(PermissionResultDeniedByRules), "denied-by-rules")] +[JsonDerivedType(typeof(PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser), "denied-no-approval-rule-and-could-not-request-from-user")] +[JsonDerivedType(typeof(PermissionResultDeniedInteractivelyByUser), "denied-interactively-by-user")] +[JsonDerivedType(typeof(PermissionResultDeniedByContentExclusionPolicy), "denied-by-content-exclusion-policy")] +[JsonDerivedType(typeof(PermissionResultDeniedByPermissionRequestHook), "denied-by-permission-request-hook")] +public partial class PermissionResult { - /// The outcome of the permission request. + /// The type discriminator. [JsonPropertyName("kind")] - public required PermissionCompletedKind Kind { get; set; } + public virtual string Kind { get; set; } = string.Empty; } + /// JSON Schema describing the form fields to present to the user (form mode only). /// Nested data type for ElicitationRequestedSchema. public partial class ElicitationRequestedSchema @@ -4707,36 +5002,6 @@ public enum PermissionPromptRequestPathAccessKind Write, } -/// The outcome of the permission request. -[JsonConverter(typeof(JsonStringEnumConverter))] -public enum PermissionCompletedKind -{ - /// The approved variant. - [JsonStringEnumMemberName("approved")] - Approved, - /// The approved-for-session variant. - [JsonStringEnumMemberName("approved-for-session")] - ApprovedForSession, - /// The approved-for-location variant. - [JsonStringEnumMemberName("approved-for-location")] - ApprovedForLocation, - /// The denied-by-rules variant. - [JsonStringEnumMemberName("denied-by-rules")] - DeniedByRules, - /// The denied-no-approval-rule-and-could-not-request-from-user variant. - [JsonStringEnumMemberName("denied-no-approval-rule-and-could-not-request-from-user")] - DeniedNoApprovalRuleAndCouldNotRequestFromUser, - /// The denied-interactively-by-user variant. - [JsonStringEnumMemberName("denied-interactively-by-user")] - DeniedInteractivelyByUser, - /// The denied-by-content-exclusion-policy variant. - [JsonStringEnumMemberName("denied-by-content-exclusion-policy")] - DeniedByContentExclusionPolicy, - /// The denied-by-permission-request-hook variant. - [JsonStringEnumMemberName("denied-by-permission-request-hook")] - DeniedByPermissionRequestHook, -} - /// Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. [JsonConverter(typeof(JsonStringEnumConverter))] public enum ElicitationRequestedMode @@ -4923,7 +5188,6 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(PendingMessagesModifiedEvent))] [JsonSerializable(typeof(PermissionCompletedData))] [JsonSerializable(typeof(PermissionCompletedEvent))] -[JsonSerializable(typeof(PermissionCompletedResult))] [JsonSerializable(typeof(PermissionPromptRequest))] [JsonSerializable(typeof(PermissionPromptRequestCommands))] [JsonSerializable(typeof(PermissionPromptRequestCustomTool))] @@ -4947,6 +5211,17 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(PermissionRequestWrite))] [JsonSerializable(typeof(PermissionRequestedData))] [JsonSerializable(typeof(PermissionRequestedEvent))] +[JsonSerializable(typeof(PermissionResult))] +[JsonSerializable(typeof(PermissionResultApproved))] +[JsonSerializable(typeof(PermissionResultApprovedForLocation))] +[JsonSerializable(typeof(PermissionResultApprovedForSession))] +[JsonSerializable(typeof(PermissionResultCancelled))] +[JsonSerializable(typeof(PermissionResultDeniedByContentExclusionPolicy))] +[JsonSerializable(typeof(PermissionResultDeniedByPermissionRequestHook))] +[JsonSerializable(typeof(PermissionResultDeniedByRules))] +[JsonSerializable(typeof(PermissionResultDeniedInteractivelyByUser))] +[JsonSerializable(typeof(PermissionResultDeniedNoApprovalRuleAndCouldNotRequestFromUser))] +[JsonSerializable(typeof(PermissionRule))] [JsonSerializable(typeof(SamplingCompletedData))] [JsonSerializable(typeof(SamplingCompletedEvent))] [JsonSerializable(typeof(SamplingRequestedData))] @@ -5011,7 +5286,9 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(ShutdownCodeChanges))] [JsonSerializable(typeof(ShutdownModelMetric))] [JsonSerializable(typeof(ShutdownModelMetricRequests))] +[JsonSerializable(typeof(ShutdownModelMetricTokenDetail))] [JsonSerializable(typeof(ShutdownModelMetricUsage))] +[JsonSerializable(typeof(ShutdownTokenDetail))] [JsonSerializable(typeof(SkillInvokedData))] [JsonSerializable(typeof(SkillInvokedEvent))] [JsonSerializable(typeof(SkillsLoadedSkill))] @@ -5073,6 +5350,13 @@ public enum ExtensionsLoadedExtensionStatus [JsonSerializable(typeof(UserMessageAttachmentSelectionDetailsStart))] [JsonSerializable(typeof(UserMessageData))] [JsonSerializable(typeof(UserMessageEvent))] +[JsonSerializable(typeof(UserToolSessionApproval))] +[JsonSerializable(typeof(UserToolSessionApprovalCommands))] +[JsonSerializable(typeof(UserToolSessionApprovalCustomTool))] +[JsonSerializable(typeof(UserToolSessionApprovalMcp))] +[JsonSerializable(typeof(UserToolSessionApprovalMemory))] +[JsonSerializable(typeof(UserToolSessionApprovalRead))] +[JsonSerializable(typeof(UserToolSessionApprovalWrite))] [JsonSerializable(typeof(WorkingDirectoryContext))] [JsonSerializable(typeof(JsonElement))] internal partial class SessionEventsJsonContext : JsonSerializerContext; \ No newline at end of file diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 03589e16d..f674a9404 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -2005,6 +2005,7 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) DisabledSkills = other.DisabledSkills is not null ? [.. other.DisabledSkills] : null; DisableResume = other.DisableResume; EnableConfigDiscovery = other.EnableConfigDiscovery; + ContinuePendingWork = other.ContinuePendingWork; ExcludedTools = other.ExcludedTools is not null ? [.. other.ExcludedTools] : null; Hooks = other.Hooks; InfiniteSessions = other.InfiniteSessions; @@ -2140,6 +2141,20 @@ protected ResumeSessionConfig(ResumeSessionConfig? other) /// public bool DisableResume { get; set; } + /// + /// When , instructs the runtime to continue any tool calls + /// or permission prompts that were still pending when the session was last suspended. + /// When (the default), the runtime treats pending work as + /// interrupted on resume. + /// + /// For permission requests, the runtime re-emits permission.requested so the + /// registered handler can re-prompt; for external + /// tool calls, the consumer is expected to supply the result via the corresponding + /// low-level RPC method. + /// + /// + public bool? ContinuePendingWork { get; set; } + /// /// Enable streaming of assistant message and reasoning chunks. /// When true, assistant.message_delta and assistant.reasoning_delta events diff --git a/dotnet/test/CloneTests.cs b/dotnet/test/CloneTests.cs index 8ed45b062..7d25cbcae 100644 --- a/dotnet/test/CloneTests.cs +++ b/dotnet/test/CloneTests.cs @@ -303,4 +303,27 @@ public void ResumeSessionConfig_Clone_PreservesIncludeSubAgentStreamingEventsDef Assert.True(clone.IncludeSubAgentStreamingEvents); } + + [Fact] + public void ResumeSessionConfig_Clone_CopiesContinuePendingWork() + { + var original = new ResumeSessionConfig + { + ContinuePendingWork = true, + }; + + var clone = original.Clone(); + + Assert.True(clone.ContinuePendingWork); + } + + [Fact] + public void ResumeSessionConfig_Clone_PreservesContinuePendingWorkDefault() + { + var original = new ResumeSessionConfig(); + + var clone = original.Clone(); + + Assert.Null(clone.ContinuePendingWork); + } } diff --git a/dotnet/test/Harness/TestHelper.cs b/dotnet/test/Harness/TestHelper.cs index 36c9be043..52e681b88 100644 --- a/dotnet/test/Harness/TestHelper.cs +++ b/dotnet/test/Harness/TestHelper.cs @@ -6,13 +6,19 @@ namespace GitHub.Copilot.SDK.Test.Harness; public static class TestHelper { + // Default tolerates CLI / replay-proxy cold start on Windows GitHub Actions + // runners, where the first test in a fixture can take ~60s before the first + // assistant message arrives. Subsequent tests in the same fixture typically + // complete in well under a second. + private static readonly TimeSpan DefaultEventTimeout = TimeSpan.FromSeconds(120); + public static async Task GetFinalAssistantMessageAsync( CopilotSession session, TimeSpan? timeout = null, bool alreadyIdle = false) { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var cts = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(60)); + using var cts = new CancellationTokenSource(timeout ?? DefaultEventTimeout); // Both `finalAssistantMessage` and `sawIdle` are set from two threads — the // subscription callback (CLI read loop) and CheckExistingMessages (RPC reply). @@ -111,7 +117,7 @@ public static async Task GetNextEventOfTypeAsync( TimeSpan? timeout = null) where T : SessionEvent { var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - using var cts = new CancellationTokenSource(timeout ?? TimeSpan.FromSeconds(60)); + using var cts = new CancellationTokenSource(timeout ?? DefaultEventTimeout); using var subscription = session.On(evt => { diff --git a/dotnet/test/McpAndAgentsTests.cs b/dotnet/test/McpAndAgentsTests.cs index 782b01123..d72c13f51 100644 --- a/dotnet/test/McpAndAgentsTests.cs +++ b/dotnet/test/McpAndAgentsTests.cs @@ -324,7 +324,8 @@ public async Task Should_Accept_Both_MCP_Servers_And_Custom_Agents() await session.SendAsync(new MessageOptions { Prompt = "What is 7+7?" }); - var message = await TestHelper.GetFinalAssistantMessageAsync(session); + // Use a longer timeout to tolerate slower MCP server spawning on Windows. + var message = await TestHelper.GetFinalAssistantMessageAsync(session, TimeSpan.FromSeconds(120)); Assert.NotNull(message); Assert.Contains("14", message!.Data.Content); diff --git a/dotnet/test/MultiClientTests.cs b/dotnet/test/MultiClientTests.cs index f3b4186fc..2a262466e 100644 --- a/dotnet/test/MultiClientTests.cs +++ b/dotnet/test/MultiClientTests.cs @@ -194,7 +194,7 @@ public async Task One_Client_Approves_Permission_And_Both_See_The_Result() foreach (var evt in client1Events.OfType() .Concat(client2Events.OfType())) { - Assert.Equal(PermissionCompletedKind.Approved, evt.Data.Result.Kind); + Assert.IsType(evt.Data.Result); } await session2.DisposeAsync(); @@ -246,7 +246,7 @@ await session1.SendAndWaitAsync(new MessageOptions foreach (var evt in client1Events.OfType() .Concat(client2Events.OfType())) { - Assert.Equal(PermissionCompletedKind.DeniedInteractivelyByUser, evt.Data.Result.Kind); + Assert.IsType(evt.Data.Result); } await session2.DisposeAsync(); diff --git a/go/client.go b/go/client.go index abeae6db3..b05479336 100644 --- a/go/client.go +++ b/go/client.go @@ -779,6 +779,9 @@ func (c *Client) ResumeSessionWithOptions(ctx context.Context, sessionID string, if config.DisableResume { req.DisableResume = Bool(true) } + if config.ContinuePendingWork { + req.ContinuePendingWork = Bool(true) + } req.MCPServers = config.MCPServers req.EnvValueMode = "direct" req.CustomAgents = config.CustomAgents diff --git a/go/client_test.go b/go/client_test.go index 83e791333..1e90b64ab 100644 --- a/go/client_test.go +++ b/go/client_test.go @@ -881,6 +881,36 @@ func TestResumeSessionRequest_RequestElicitation(t *testing.T) { }) } +func TestResumeSessionRequest_ContinuePendingWork(t *testing.T) { + t.Run("forwards continuePendingWork when true", func(t *testing.T) { + req := resumeSessionRequest{ + SessionID: "s1", + ContinuePendingWork: Bool(true), + } + data, err := json.Marshal(req) + if err != nil { + t.Fatalf("Failed to marshal: %v", err) + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + t.Fatalf("Failed to unmarshal: %v", err) + } + if m["continuePendingWork"] != true { + t.Errorf("Expected continuePendingWork to be true, got %v", m["continuePendingWork"]) + } + }) + + t.Run("omits continuePendingWork when not set", func(t *testing.T) { + req := resumeSessionRequest{SessionID: "s1"} + data, _ := json.Marshal(req) + var m map[string]any + json.Unmarshal(data, &m) + if _, ok := m["continuePendingWork"]; ok { + t.Error("Expected continuePendingWork to be omitted when not set") + } + }) +} + func TestCreateSessionRequest_IncludeSubAgentStreamingEvents(t *testing.T) { t.Run("defaults to true when nil", func(t *testing.T) { req := createSessionRequest{ diff --git a/go/generated_session_events.go b/go/generated_session_events.go index c9fb889a2..5fd844aae 100644 --- a/go/generated_session_events.go +++ b/go/generated_session_events.go @@ -694,6 +694,8 @@ type AssistantMessageData struct { RequestID *string `json:"requestId,omitempty"` // Tool invocations requested by the assistant in this message ToolRequests []AssistantMessageToolRequest `json:"toolRequests,omitempty"` + // Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event + TurnID *string `json:"turnId,omitempty"` } func (*AssistantMessageData) sessionEventData() {} @@ -1080,7 +1082,7 @@ type PermissionCompletedData struct { // Request ID of the resolved permission request; clients should dismiss any UI for this request RequestID string `json:"requestId"` // The result of the permission request - Result PermissionCompletedResult `json:"result"` + Result PermissionResult `json:"result"` // Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts ToolCallID *string `json:"toolCallId,omitempty"` } @@ -1261,6 +1263,8 @@ type SessionResumeData struct { AlreadyInUse *bool `json:"alreadyInUse,omitempty"` // Updated working directory and git context at resume time Context *WorkingDirectoryContext `json:"context,omitempty"` + // When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + 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. "low", "medium", "high", "xhigh") @@ -1271,6 +1275,8 @@ type SessionResumeData struct { ResumeTime time.Time `json:"resumeTime"` // Model currently selected at resume time SelectedModel *string `json:"selectedModel,omitempty"` + // True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + SessionWasActive *bool `json:"sessionWasActive,omitempty"` } func (*SessionResumeData) sessionEventData() {} @@ -1305,10 +1311,14 @@ type SessionShutdownData struct { ShutdownType ShutdownType `json:"shutdownType"` // System message token count at shutdown SystemTokens *float64 `json:"systemTokens,omitempty"` + // Session-wide per-token-type accumulated token counts + TokenDetails map[string]ShutdownTokenDetail `json:"tokenDetails,omitempty"` // Tool definitions token count at shutdown ToolDefinitionsTokens *float64 `json:"toolDefinitionsTokens,omitempty"` // Cumulative time spent in API calls during the session, in milliseconds TotalAPIDurationMs float64 `json:"totalApiDurationMs"` + // Session-wide accumulated nano-AI units cost + TotalNanoAiu *float64 `json:"totalNanoAiu,omitempty"` // Total number of premium API requests used during the session TotalPremiumRequests float64 `json:"totalPremiumRequests"` } @@ -1554,6 +1564,8 @@ type ToolExecutionCompleteData struct { ToolCallID string `json:"toolCallId"` // Tool-specific telemetry data (e.g., CodeQL check counts, grep match counts) ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` + // Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event + TurnID *string `json:"turnId,omitempty"` } func (*ToolExecutionCompleteData) sessionEventData() {} @@ -1583,6 +1595,8 @@ type ToolExecutionStartData struct { ToolCallID string `json:"toolCallId"` // Name of the tool being executed ToolName string `json:"toolName"` + // Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event + TurnID *string `json:"turnId,omitempty"` } func (*ToolExecutionStartData) sessionEventData() {} @@ -1665,6 +1679,8 @@ type UserMessageData struct { InteractionID *string `json:"interactionId,omitempty"` // Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitempty"` + // Parent agent task ID for background telemetry correlated to this user turn + ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"` // Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) Source *string `json:"source,omitempty"` // Normalized document MIME types that were sent natively instead of through tagged_files XML @@ -1995,7 +2011,7 @@ type UserMessageAttachmentFileLineRange struct { type AssistantUsageCopilotUsage struct { // Itemized token usage breakdown TokenDetails []AssistantUsageCopilotUsageTokenDetail `json:"tokenDetails"` - // Total cost in nano-AIU (AI Units) for this request + // Total cost in nano-AI units for this request TotalNanoAiu float64 `json:"totalNanoAiu"` } @@ -2003,7 +2019,7 @@ type AssistantUsageCopilotUsage struct { type CompactionCompleteCompactionTokensUsedCopilotUsage struct { // Itemized token usage breakdown TokenDetails []CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail `json:"tokenDetails"` - // Total cost in nano-AIU (AI Units) for this request + // Total cost in nano-AI units for this request TotalNanoAiu float64 `json:"totalNanoAiu"` } @@ -2083,10 +2099,40 @@ type SystemNotification struct { TriggerTool *string `json:"triggerTool,omitempty"` } +// The approval to add as a session-scoped rule +type UserToolSessionApproval struct { + // Kind discriminator + Kind UserToolSessionApprovalKind `json:"kind"` + // Command identifiers approved by the user + CommandIdentifiers []string `json:"commandIdentifiers,omitempty"` + // MCP server name + ServerName *string `json:"serverName,omitempty"` + // Optional MCP tool name, or null for all tools on the server + ToolName *string `json:"toolName,omitempty"` +} + // The result of the permission request -type PermissionCompletedResult struct { - // The outcome of the permission request - Kind PermissionCompletedKind `json:"kind"` +type PermissionResult struct { + // Kind discriminator + Kind PermissionResultKind `json:"kind"` + // The approval to add as a session-scoped rule + Approval *UserToolSessionApproval `json:"approval,omitempty"` + // Optional feedback from the user explaining the denial + Feedback *string `json:"feedback,omitempty"` + // Whether to force-reject the current agent turn + ForceReject *bool `json:"forceReject,omitempty"` + // Whether to interrupt the current agent turn + Interrupt *bool `json:"interrupt,omitempty"` + // The location key (git root or cwd) to persist the approval to + LocationKey *string `json:"locationKey,omitempty"` + // Human-readable explanation of why the path was excluded + Message *string `json:"message,omitempty"` + // File path that triggered the exclusion + Path *string `json:"path,omitempty"` + // Optional explanation of why the request was cancelled + Reason *string `json:"reason,omitempty"` + // Rules that denied the request + Rules []PermissionRule `json:"rules,omitempty"` } // Token usage breakdown @@ -2258,13 +2304,34 @@ type PermissionRequestShellPossibleURL struct { URL string `json:"url"` } +type PermissionRule struct { + // Optional rule argument matched against the request + Argument *string `json:"argument"` + // The rule kind, such as Shell or GitHubMCP + Kind string `json:"kind"` +} + type ShutdownModelMetric struct { // Request count and cost metrics Requests ShutdownModelMetricRequests `json:"requests"` + // Token count details per type + TokenDetails map[string]ShutdownModelMetricTokenDetail `json:"tokenDetails,omitempty"` + // Accumulated nano-AI units cost for this model + TotalNanoAiu *float64 `json:"totalNanoAiu,omitempty"` // Token usage breakdown Usage ShutdownModelMetricUsage `json:"usage"` } +type ShutdownModelMetricTokenDetail struct { + // Accumulated token count for this token type + TokenCount float64 `json:"tokenCount"` +} + +type ShutdownTokenDetail struct { + // Accumulated token count for this token type + TokenCount float64 `json:"tokenCount"` +} + type SkillsLoadedSkill struct { // Description of what the skill does Description string `json:"description"` @@ -2355,6 +2422,33 @@ const ( PermissionRequestKindHook PermissionRequestKind = "hook" ) +// Kind discriminator for PermissionResult. +type PermissionResultKind string + +const ( + PermissionResultKindApproved PermissionResultKind = "approved" + PermissionResultKindApprovedForSession PermissionResultKind = "approved-for-session" + PermissionResultKindApprovedForLocation PermissionResultKind = "approved-for-location" + PermissionResultKindCancelled PermissionResultKind = "cancelled" + PermissionResultKindDeniedByRules PermissionResultKind = "denied-by-rules" + PermissionResultKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionResultKind = "denied-no-approval-rule-and-could-not-request-from-user" + PermissionResultKindDeniedInteractivelyByUser PermissionResultKind = "denied-interactively-by-user" + PermissionResultKindDeniedByContentExclusionPolicy PermissionResultKind = "denied-by-content-exclusion-policy" + PermissionResultKindDeniedByPermissionRequestHook PermissionResultKind = "denied-by-permission-request-hook" +) + +// Kind discriminator for UserToolSessionApproval. +type UserToolSessionApprovalKind string + +const ( + UserToolSessionApprovalKindCommands UserToolSessionApprovalKind = "commands" + UserToolSessionApprovalKindRead UserToolSessionApprovalKind = "read" + UserToolSessionApprovalKindWrite UserToolSessionApprovalKind = "write" + UserToolSessionApprovalKindMcp UserToolSessionApprovalKind = "mcp" + UserToolSessionApprovalKindMemory UserToolSessionApprovalKind = "memory" + UserToolSessionApprovalKindCustomTool UserToolSessionApprovalKind = "custom-tool" +) + // Message role: "system" for system prompts, "developer" for developer-injected instructions type SystemMessageRole string @@ -2393,20 +2487,6 @@ const ( UserMessageAgentModeShell UserMessageAgentMode = "shell" ) -// The outcome of the permission request -type PermissionCompletedKind string - -const ( - PermissionCompletedKindApproved PermissionCompletedKind = "approved" - PermissionCompletedKindApprovedForSession PermissionCompletedKind = "approved-for-session" - PermissionCompletedKindApprovedForLocation PermissionCompletedKind = "approved-for-location" - PermissionCompletedKindDeniedByRules PermissionCompletedKind = "denied-by-rules" - PermissionCompletedKindDeniedNoApprovalRuleAndCouldNotRequestFromUser PermissionCompletedKind = "denied-no-approval-rule-and-could-not-request-from-user" - PermissionCompletedKindDeniedInteractivelyByUser PermissionCompletedKind = "denied-interactively-by-user" - PermissionCompletedKindDeniedByContentExclusionPolicy PermissionCompletedKind = "denied-by-content-exclusion-policy" - PermissionCompletedKindDeniedByPermissionRequestHook PermissionCompletedKind = "denied-by-permission-request-hook" -) - // The type of operation performed on the plan file type PlanChangedOperation string diff --git a/go/rpc/generated_rpc.go b/go/rpc/generated_rpc.go index 311081247..c56418a24 100644 --- a/go/rpc/generated_rpc.go +++ b/go/rpc/generated_rpc.go @@ -13,233 +13,248 @@ import ( ) type RPCTypes struct { - AccountGetQuotaRequest AccountGetQuotaRequest `json:"AccountGetQuotaRequest"` - AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` - AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` - AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` - AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` - AgentInfo AgentInfo `json:"AgentInfo"` - AgentList AgentList `json:"AgentList"` - AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` - AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` - AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` - AuthInfoType AuthInfoType `json:"AuthInfoType"` - CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` - CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` - CurrentModel CurrentModel `json:"CurrentModel"` - DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` - DiscoveredMCPServerSource MCPServerSource `json:"DiscoveredMcpServerSource"` - DiscoveredMCPServerType DiscoveredMCPServerType `json:"DiscoveredMcpServerType"` - Extension Extension `json:"Extension"` - ExtensionList ExtensionList `json:"ExtensionList"` - ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` - ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` - ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` - ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` - ExtensionSource ExtensionSource `json:"ExtensionSource"` - ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` - ExtensionStatus ExtensionStatus `json:"ExtensionStatus"` - FilterMapping *FilterMapping `json:"FilterMapping"` - FilterMappingString FilterMappingString `json:"FilterMappingString"` - FilterMappingValue FilterMappingString `json:"FilterMappingValue"` - FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` - FleetStartResult FleetStartResult `json:"FleetStartResult"` - HandleToolCallResult HandleToolCallResult `json:"HandleToolCallResult"` - HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` - HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` - HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` - HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` - InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` - InstructionsSources InstructionsSources `json:"InstructionsSources"` - InstructionsSourcesLocation InstructionsSourcesLocation `json:"InstructionsSourcesLocation"` - InstructionsSourcesType InstructionsSourcesType `json:"InstructionsSourcesType"` - LogRequest LogRequest `json:"LogRequest"` - LogResult LogResult `json:"LogResult"` - MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` - MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` - MCPConfigDisableRequest MCPConfigDisableRequest `json:"McpConfigDisableRequest"` - MCPConfigDisableResult MCPConfigDisableResult `json:"McpConfigDisableResult"` - MCPConfigEnableRequest MCPConfigEnableRequest `json:"McpConfigEnableRequest"` - MCPConfigEnableResult MCPConfigEnableResult `json:"McpConfigEnableResult"` - MCPConfigList MCPConfigList `json:"McpConfigList"` - MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` - MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` - MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` - MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` - MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` - MCPDisableResult MCPDisableResult `json:"McpDisableResult"` - MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` - MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` - MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` - MCPEnableResult MCPEnableResult `json:"McpEnableResult"` - MCPOauthLoginRequest MCPOauthLoginRequest `json:"McpOauthLoginRequest"` - MCPOauthLoginResult MCPOauthLoginResult `json:"McpOauthLoginResult"` - MCPReloadResult MCPReloadResult `json:"McpReloadResult"` - MCPServer MCPServer `json:"McpServer"` - MCPServerConfig MCPServerConfig `json:"McpServerConfig"` - MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` - MCPServerConfigHTTPType MCPServerConfigHTTPType `json:"McpServerConfigHttpType"` - MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` - MCPServerConfigLocalType MCPServerConfigLocalType `json:"McpServerConfigLocalType"` - MCPServerList MCPServerList `json:"McpServerList"` - MCPServerSource MCPServerSource `json:"McpServerSource"` - MCPServerStatus MCPServerStatus `json:"McpServerStatus"` - Model ModelElement `json:"Model"` - ModelBilling ModelBilling `json:"ModelBilling"` - ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` - ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` - ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` - ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` - ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` - ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` - ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` - ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` - ModelList ModelList `json:"ModelList"` - ModelPolicy ModelPolicy `json:"ModelPolicy"` - ModelsListRequest ModelsListRequest `json:"ModelsListRequest"` - ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` - ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` - ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` - ModeSetResult ModeSetResult `json:"ModeSetResult"` - NameGetResult NameGetResult `json:"NameGetResult"` - NameSetRequest NameSetRequest `json:"NameSetRequest"` - NameSetResult NameSetResult `json:"NameSetResult"` - PermissionDecision PermissionDecision `json:"PermissionDecision"` - PermissionDecisionApproveForLocation PermissionDecisionApproveForLocation `json:"PermissionDecisionApproveForLocation"` - PermissionDecisionApproveForLocationApproval PermissionDecisionApproveForLocationApproval `json:"PermissionDecisionApproveForLocationApproval"` - PermissionDecisionApproveForLocationApprovalCommands PermissionDecisionApproveForLocationApprovalCommands `json:"PermissionDecisionApproveForLocationApprovalCommands"` - PermissionDecisionApproveForLocationApprovalCustomTool PermissionDecisionApproveForLocationApprovalCustomTool `json:"PermissionDecisionApproveForLocationApprovalCustomTool"` - PermissionDecisionApproveForLocationApprovalMCP PermissionDecisionApproveForLocationApprovalMCP `json:"PermissionDecisionApproveForLocationApprovalMcp"` - PermissionDecisionApproveForLocationApprovalMCPSampling PermissionDecisionApproveForLocationApprovalMCPSampling `json:"PermissionDecisionApproveForLocationApprovalMcpSampling"` - PermissionDecisionApproveForLocationApprovalMemory PermissionDecisionApproveForLocationApprovalMemory `json:"PermissionDecisionApproveForLocationApprovalMemory"` - PermissionDecisionApproveForLocationApprovalRead PermissionDecisionApproveForLocationApprovalRead `json:"PermissionDecisionApproveForLocationApprovalRead"` - PermissionDecisionApproveForLocationApprovalWrite PermissionDecisionApproveForLocationApprovalWrite `json:"PermissionDecisionApproveForLocationApprovalWrite"` - PermissionDecisionApproveForSession PermissionDecisionApproveForSession `json:"PermissionDecisionApproveForSession"` - PermissionDecisionApproveForSessionApproval PermissionDecisionApproveForSessionApproval `json:"PermissionDecisionApproveForSessionApproval"` - PermissionDecisionApproveForSessionApprovalCommands PermissionDecisionApproveForSessionApprovalCommands `json:"PermissionDecisionApproveForSessionApprovalCommands"` - PermissionDecisionApproveForSessionApprovalCustomTool PermissionDecisionApproveForSessionApprovalCustomTool `json:"PermissionDecisionApproveForSessionApprovalCustomTool"` - PermissionDecisionApproveForSessionApprovalMCP PermissionDecisionApproveForSessionApprovalMCP `json:"PermissionDecisionApproveForSessionApprovalMcp"` - PermissionDecisionApproveForSessionApprovalMCPSampling PermissionDecisionApproveForSessionApprovalMCPSampling `json:"PermissionDecisionApproveForSessionApprovalMcpSampling"` - PermissionDecisionApproveForSessionApprovalMemory PermissionDecisionApproveForSessionApprovalMemory `json:"PermissionDecisionApproveForSessionApprovalMemory"` - PermissionDecisionApproveForSessionApprovalRead PermissionDecisionApproveForSessionApprovalRead `json:"PermissionDecisionApproveForSessionApprovalRead"` - PermissionDecisionApproveForSessionApprovalWrite PermissionDecisionApproveForSessionApprovalWrite `json:"PermissionDecisionApproveForSessionApprovalWrite"` - PermissionDecisionApproveOnce PermissionDecisionApproveOnce `json:"PermissionDecisionApproveOnce"` - PermissionDecisionReject PermissionDecisionReject `json:"PermissionDecisionReject"` - PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` - PermissionDecisionUserNotAvailable PermissionDecisionUserNotAvailable `json:"PermissionDecisionUserNotAvailable"` - PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` - PermissionsResetSessionApprovalsRequest PermissionsResetSessionApprovalsRequest `json:"PermissionsResetSessionApprovalsRequest"` - PermissionsResetSessionApprovalsResult PermissionsResetSessionApprovalsResult `json:"PermissionsResetSessionApprovalsResult"` - PermissionsSetApproveAllRequest PermissionsSetApproveAllRequest `json:"PermissionsSetApproveAllRequest"` - PermissionsSetApproveAllResult PermissionsSetApproveAllResult `json:"PermissionsSetApproveAllResult"` - PingRequest PingRequest `json:"PingRequest"` - PingResult PingResult `json:"PingResult"` - PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` - PlanReadResult PlanReadResult `json:"PlanReadResult"` - PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` - PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` - Plugin PluginElement `json:"Plugin"` - PluginList PluginList `json:"PluginList"` - ServerSkill ServerSkill `json:"ServerSkill"` - ServerSkillList ServerSkillList `json:"ServerSkillList"` - SessionAuthStatus SessionAuthStatus `json:"SessionAuthStatus"` - SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` - SessionFSError SessionFSError `json:"SessionFsError"` - SessionFSErrorCode SessionFSErrorCode `json:"SessionFsErrorCode"` - SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` - SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` - SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` - SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` - SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` - SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` - SessionFSReaddirWithTypesEntryType SessionFSReaddirWithTypesEntryType `json:"SessionFsReaddirWithTypesEntryType"` - SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` - SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` - SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` - SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` - SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` - SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` - SessionFSSetProviderConventions SessionFSSetProviderConventions `json:"SessionFsSetProviderConventions"` - SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` - SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` - SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` - SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` - SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` - SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` - SessionMode SessionMode `json:"SessionMode"` - SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` - SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` - ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` - ShellExecResult ShellExecResult `json:"ShellExecResult"` - ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` - ShellKillResult ShellKillResult `json:"ShellKillResult"` - ShellKillSignal ShellKillSignal `json:"ShellKillSignal"` - Skill Skill `json:"Skill"` - SkillList SkillList `json:"SkillList"` - SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` - SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` - SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` - SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` - SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` - SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` - SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` - SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` - TaskAgentInfo TaskAgentInfo `json:"TaskAgentInfo"` - TaskAgentInfoExecutionMode TaskInfoExecutionMode `json:"TaskAgentInfoExecutionMode"` - TaskAgentInfoStatus TaskInfoStatus `json:"TaskAgentInfoStatus"` - TaskInfo TaskInfo `json:"TaskInfo"` - TaskList TaskList `json:"TaskList"` - TasksCancelRequest TasksCancelRequest `json:"TasksCancelRequest"` - TasksCancelResult TasksCancelResult `json:"TasksCancelResult"` - TaskShellInfo TaskShellInfo `json:"TaskShellInfo"` - TaskShellInfoAttachmentMode TaskShellInfoAttachmentMode `json:"TaskShellInfoAttachmentMode"` - TaskShellInfoExecutionMode TaskInfoExecutionMode `json:"TaskShellInfoExecutionMode"` - TaskShellInfoStatus TaskInfoStatus `json:"TaskShellInfoStatus"` - TasksPromoteToBackgroundRequest TasksPromoteToBackgroundRequest `json:"TasksPromoteToBackgroundRequest"` - TasksPromoteToBackgroundResult TasksPromoteToBackgroundResult `json:"TasksPromoteToBackgroundResult"` - TasksRemoveRequest TasksRemoveRequest `json:"TasksRemoveRequest"` - TasksRemoveResult TasksRemoveResult `json:"TasksRemoveResult"` - TasksStartAgentRequest TasksStartAgentRequest `json:"TasksStartAgentRequest"` - TasksStartAgentResult TasksStartAgentResult `json:"TasksStartAgentResult"` - Tool Tool `json:"Tool"` - ToolCallResult ToolCallResult `json:"ToolCallResult"` - ToolList ToolList `json:"ToolList"` - ToolsHandlePendingToolCall *ToolsHandlePendingToolCall `json:"ToolsHandlePendingToolCall"` - ToolsHandlePendingToolCallRequest ToolsHandlePendingToolCallRequest `json:"ToolsHandlePendingToolCallRequest"` - ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` - UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` - UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` - UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` - UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` - UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` - UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` - UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` - UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` - UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` - UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` - UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` - UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` - UIElicitationSchemaProperty UIElicitationSchemaProperty `json:"UIElicitationSchemaProperty"` - UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` - UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` - UIElicitationSchemaPropertyNumberType UIElicitationSchemaPropertyNumberTypeEnum `json:"UIElicitationSchemaPropertyNumberType"` - UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` - UIElicitationSchemaPropertyStringFormat UIElicitationSchemaPropertyStringFormat `json:"UIElicitationSchemaPropertyStringFormat"` - UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` - UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` - UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` - UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` - UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` - UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` - UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` - UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` - UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` - WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` - WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` - WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` - WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` - WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` - WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` + AccountGetQuotaRequest AccountGetQuotaRequest `json:"AccountGetQuotaRequest"` + AccountGetQuotaResult AccountGetQuotaResult `json:"AccountGetQuotaResult"` + AccountQuotaSnapshot AccountQuotaSnapshot `json:"AccountQuotaSnapshot"` + AgentDeselectResult AgentDeselectResult `json:"AgentDeselectResult"` + AgentGetCurrentResult AgentGetCurrentResult `json:"AgentGetCurrentResult"` + AgentInfo AgentInfo `json:"AgentInfo"` + AgentList AgentList `json:"AgentList"` + AgentReloadResult AgentReloadResult `json:"AgentReloadResult"` + AgentSelectRequest AgentSelectRequest `json:"AgentSelectRequest"` + AgentSelectResult AgentSelectResult `json:"AgentSelectResult"` + AuthInfoType AuthInfoType `json:"AuthInfoType"` + CommandsHandlePendingCommandRequest CommandsHandlePendingCommandRequest `json:"CommandsHandlePendingCommandRequest"` + CommandsHandlePendingCommandResult CommandsHandlePendingCommandResult `json:"CommandsHandlePendingCommandResult"` + CurrentModel CurrentModel `json:"CurrentModel"` + DiscoveredMCPServer DiscoveredMCPServer `json:"DiscoveredMcpServer"` + DiscoveredMCPServerSource MCPServerSource `json:"DiscoveredMcpServerSource"` + DiscoveredMCPServerType DiscoveredMCPServerType `json:"DiscoveredMcpServerType"` + EmbeddedBlobResourceContents EmbeddedBlobResourceContents `json:"EmbeddedBlobResourceContents"` + EmbeddedTextResourceContents EmbeddedTextResourceContents `json:"EmbeddedTextResourceContents"` + Extension Extension `json:"Extension"` + ExtensionList ExtensionList `json:"ExtensionList"` + ExtensionsDisableRequest ExtensionsDisableRequest `json:"ExtensionsDisableRequest"` + ExtensionsDisableResult ExtensionsDisableResult `json:"ExtensionsDisableResult"` + ExtensionsEnableRequest ExtensionsEnableRequest `json:"ExtensionsEnableRequest"` + ExtensionsEnableResult ExtensionsEnableResult `json:"ExtensionsEnableResult"` + ExtensionSource ExtensionSource `json:"ExtensionSource"` + ExtensionsReloadResult ExtensionsReloadResult `json:"ExtensionsReloadResult"` + ExtensionStatus ExtensionStatus `json:"ExtensionStatus"` + ExternalToolResult *ExternalToolResult `json:"ExternalToolResult"` + ExternalToolTextResultForLlm ExternalToolTextResultForLlm `json:"ExternalToolTextResultForLlm"` + ExternalToolTextResultForLlmContent ExternalToolTextResultForLlmContent `json:"ExternalToolTextResultForLlmContent"` + ExternalToolTextResultForLlmContentAudio ExternalToolTextResultForLlmContentAudio `json:"ExternalToolTextResultForLlmContentAudio"` + ExternalToolTextResultForLlmContentImage ExternalToolTextResultForLlmContentImage `json:"ExternalToolTextResultForLlmContentImage"` + ExternalToolTextResultForLlmContentResource ExternalToolTextResultForLlmContentResource `json:"ExternalToolTextResultForLlmContentResource"` + ExternalToolTextResultForLlmContentResourceDetails ExternalToolTextResultForLlmContentResourceDetails `json:"ExternalToolTextResultForLlmContentResourceDetails"` + ExternalToolTextResultForLlmContentResourceLink ExternalToolTextResultForLlmContentResourceLink `json:"ExternalToolTextResultForLlmContentResourceLink"` + ExternalToolTextResultForLlmContentResourceLinkIcon ExternalToolTextResultForLlmContentResourceLinkIcon `json:"ExternalToolTextResultForLlmContentResourceLinkIcon"` + ExternalToolTextResultForLlmContentResourceLinkIconTheme ExternalToolTextResultForLlmContentResourceLinkIconTheme `json:"ExternalToolTextResultForLlmContentResourceLinkIconTheme"` + ExternalToolTextResultForLlmContentTerminal ExternalToolTextResultForLlmContentTerminal `json:"ExternalToolTextResultForLlmContentTerminal"` + ExternalToolTextResultForLlmContentText ExternalToolTextResultForLlmContentText `json:"ExternalToolTextResultForLlmContentText"` + FilterMapping *FilterMapping `json:"FilterMapping"` + FilterMappingString FilterMappingString `json:"FilterMappingString"` + FilterMappingValue FilterMappingString `json:"FilterMappingValue"` + FleetStartRequest FleetStartRequest `json:"FleetStartRequest"` + FleetStartResult FleetStartResult `json:"FleetStartResult"` + HandlePendingToolCallRequest HandlePendingToolCallRequest `json:"HandlePendingToolCallRequest"` + HandlePendingToolCallResult HandlePendingToolCallResult `json:"HandlePendingToolCallResult"` + HistoryCompactContextWindow HistoryCompactContextWindow `json:"HistoryCompactContextWindow"` + HistoryCompactResult HistoryCompactResult `json:"HistoryCompactResult"` + HistoryTruncateRequest HistoryTruncateRequest `json:"HistoryTruncateRequest"` + HistoryTruncateResult HistoryTruncateResult `json:"HistoryTruncateResult"` + InstructionsGetSourcesResult InstructionsGetSourcesResult `json:"InstructionsGetSourcesResult"` + InstructionsSources InstructionsSources `json:"InstructionsSources"` + InstructionsSourcesLocation InstructionsSourcesLocation `json:"InstructionsSourcesLocation"` + InstructionsSourcesType InstructionsSourcesType `json:"InstructionsSourcesType"` + LogRequest LogRequest `json:"LogRequest"` + LogResult LogResult `json:"LogResult"` + MCPConfigAddRequest MCPConfigAddRequest `json:"McpConfigAddRequest"` + MCPConfigAddResult MCPConfigAddResult `json:"McpConfigAddResult"` + MCPConfigDisableRequest MCPConfigDisableRequest `json:"McpConfigDisableRequest"` + MCPConfigDisableResult MCPConfigDisableResult `json:"McpConfigDisableResult"` + MCPConfigEnableRequest MCPConfigEnableRequest `json:"McpConfigEnableRequest"` + MCPConfigEnableResult MCPConfigEnableResult `json:"McpConfigEnableResult"` + MCPConfigList MCPConfigList `json:"McpConfigList"` + MCPConfigRemoveRequest MCPConfigRemoveRequest `json:"McpConfigRemoveRequest"` + MCPConfigRemoveResult MCPConfigRemoveResult `json:"McpConfigRemoveResult"` + MCPConfigUpdateRequest MCPConfigUpdateRequest `json:"McpConfigUpdateRequest"` + MCPConfigUpdateResult MCPConfigUpdateResult `json:"McpConfigUpdateResult"` + MCPDisableRequest MCPDisableRequest `json:"McpDisableRequest"` + MCPDisableResult MCPDisableResult `json:"McpDisableResult"` + MCPDiscoverRequest MCPDiscoverRequest `json:"McpDiscoverRequest"` + MCPDiscoverResult MCPDiscoverResult `json:"McpDiscoverResult"` + MCPEnableRequest MCPEnableRequest `json:"McpEnableRequest"` + MCPEnableResult MCPEnableResult `json:"McpEnableResult"` + MCPOauthLoginRequest MCPOauthLoginRequest `json:"McpOauthLoginRequest"` + MCPOauthLoginResult MCPOauthLoginResult `json:"McpOauthLoginResult"` + MCPReloadResult MCPReloadResult `json:"McpReloadResult"` + MCPServer MCPServer `json:"McpServer"` + MCPServerConfig MCPServerConfig `json:"McpServerConfig"` + MCPServerConfigHTTP MCPServerConfigHTTP `json:"McpServerConfigHttp"` + MCPServerConfigHTTPType MCPServerConfigHTTPType `json:"McpServerConfigHttpType"` + MCPServerConfigLocal MCPServerConfigLocal `json:"McpServerConfigLocal"` + MCPServerConfigLocalType MCPServerConfigLocalType `json:"McpServerConfigLocalType"` + MCPServerList MCPServerList `json:"McpServerList"` + MCPServerSource MCPServerSource `json:"McpServerSource"` + MCPServerStatus MCPServerStatus `json:"McpServerStatus"` + Model ModelElement `json:"Model"` + ModelBilling ModelBilling `json:"ModelBilling"` + ModelCapabilities ModelCapabilities `json:"ModelCapabilities"` + ModelCapabilitiesLimits ModelCapabilitiesLimits `json:"ModelCapabilitiesLimits"` + ModelCapabilitiesLimitsVision ModelCapabilitiesLimitsVision `json:"ModelCapabilitiesLimitsVision"` + ModelCapabilitiesOverride ModelCapabilitiesOverride `json:"ModelCapabilitiesOverride"` + ModelCapabilitiesOverrideLimits ModelCapabilitiesOverrideLimits `json:"ModelCapabilitiesOverrideLimits"` + ModelCapabilitiesOverrideLimitsVision ModelCapabilitiesOverrideLimitsVision `json:"ModelCapabilitiesOverrideLimitsVision"` + ModelCapabilitiesOverrideSupports ModelCapabilitiesOverrideSupports `json:"ModelCapabilitiesOverrideSupports"` + ModelCapabilitiesSupports ModelCapabilitiesSupports `json:"ModelCapabilitiesSupports"` + ModelList ModelList `json:"ModelList"` + ModelPolicy ModelPolicy `json:"ModelPolicy"` + ModelsListRequest ModelsListRequest `json:"ModelsListRequest"` + ModelSwitchToRequest ModelSwitchToRequest `json:"ModelSwitchToRequest"` + ModelSwitchToResult ModelSwitchToResult `json:"ModelSwitchToResult"` + ModeSetRequest ModeSetRequest `json:"ModeSetRequest"` + ModeSetResult ModeSetResult `json:"ModeSetResult"` + NameGetResult NameGetResult `json:"NameGetResult"` + NameSetRequest NameSetRequest `json:"NameSetRequest"` + NameSetResult NameSetResult `json:"NameSetResult"` + PermissionDecision PermissionDecision `json:"PermissionDecision"` + PermissionDecisionApproveForLocation PermissionDecisionApproveForLocation `json:"PermissionDecisionApproveForLocation"` + PermissionDecisionApproveForLocationApproval PermissionDecisionApproveForLocationApproval `json:"PermissionDecisionApproveForLocationApproval"` + PermissionDecisionApproveForLocationApprovalCommands PermissionDecisionApproveForLocationApprovalCommands `json:"PermissionDecisionApproveForLocationApprovalCommands"` + PermissionDecisionApproveForLocationApprovalCustomTool PermissionDecisionApproveForLocationApprovalCustomTool `json:"PermissionDecisionApproveForLocationApprovalCustomTool"` + PermissionDecisionApproveForLocationApprovalMCP PermissionDecisionApproveForLocationApprovalMCP `json:"PermissionDecisionApproveForLocationApprovalMcp"` + PermissionDecisionApproveForLocationApprovalMCPSampling PermissionDecisionApproveForLocationApprovalMCPSampling `json:"PermissionDecisionApproveForLocationApprovalMcpSampling"` + PermissionDecisionApproveForLocationApprovalMemory PermissionDecisionApproveForLocationApprovalMemory `json:"PermissionDecisionApproveForLocationApprovalMemory"` + PermissionDecisionApproveForLocationApprovalRead PermissionDecisionApproveForLocationApprovalRead `json:"PermissionDecisionApproveForLocationApprovalRead"` + PermissionDecisionApproveForLocationApprovalWrite PermissionDecisionApproveForLocationApprovalWrite `json:"PermissionDecisionApproveForLocationApprovalWrite"` + PermissionDecisionApproveForSession PermissionDecisionApproveForSession `json:"PermissionDecisionApproveForSession"` + PermissionDecisionApproveForSessionApproval PermissionDecisionApproveForSessionApproval `json:"PermissionDecisionApproveForSessionApproval"` + PermissionDecisionApproveForSessionApprovalCommands PermissionDecisionApproveForSessionApprovalCommands `json:"PermissionDecisionApproveForSessionApprovalCommands"` + PermissionDecisionApproveForSessionApprovalCustomTool PermissionDecisionApproveForSessionApprovalCustomTool `json:"PermissionDecisionApproveForSessionApprovalCustomTool"` + PermissionDecisionApproveForSessionApprovalMCP PermissionDecisionApproveForSessionApprovalMCP `json:"PermissionDecisionApproveForSessionApprovalMcp"` + PermissionDecisionApproveForSessionApprovalMCPSampling PermissionDecisionApproveForSessionApprovalMCPSampling `json:"PermissionDecisionApproveForSessionApprovalMcpSampling"` + PermissionDecisionApproveForSessionApprovalMemory PermissionDecisionApproveForSessionApprovalMemory `json:"PermissionDecisionApproveForSessionApprovalMemory"` + PermissionDecisionApproveForSessionApprovalRead PermissionDecisionApproveForSessionApprovalRead `json:"PermissionDecisionApproveForSessionApprovalRead"` + PermissionDecisionApproveForSessionApprovalWrite PermissionDecisionApproveForSessionApprovalWrite `json:"PermissionDecisionApproveForSessionApprovalWrite"` + PermissionDecisionApproveOnce PermissionDecisionApproveOnce `json:"PermissionDecisionApproveOnce"` + PermissionDecisionApprovePermanently PermissionDecisionApprovePermanently `json:"PermissionDecisionApprovePermanently"` + PermissionDecisionReject PermissionDecisionReject `json:"PermissionDecisionReject"` + PermissionDecisionRequest PermissionDecisionRequest `json:"PermissionDecisionRequest"` + PermissionDecisionUserNotAvailable PermissionDecisionUserNotAvailable `json:"PermissionDecisionUserNotAvailable"` + PermissionRequestResult PermissionRequestResult `json:"PermissionRequestResult"` + PermissionsResetSessionApprovalsRequest PermissionsResetSessionApprovalsRequest `json:"PermissionsResetSessionApprovalsRequest"` + PermissionsResetSessionApprovalsResult PermissionsResetSessionApprovalsResult `json:"PermissionsResetSessionApprovalsResult"` + PermissionsSetApproveAllRequest PermissionsSetApproveAllRequest `json:"PermissionsSetApproveAllRequest"` + PermissionsSetApproveAllResult PermissionsSetApproveAllResult `json:"PermissionsSetApproveAllResult"` + PingRequest PingRequest `json:"PingRequest"` + PingResult PingResult `json:"PingResult"` + PlanDeleteResult PlanDeleteResult `json:"PlanDeleteResult"` + PlanReadResult PlanReadResult `json:"PlanReadResult"` + PlanUpdateRequest PlanUpdateRequest `json:"PlanUpdateRequest"` + PlanUpdateResult PlanUpdateResult `json:"PlanUpdateResult"` + Plugin PluginElement `json:"Plugin"` + PluginList PluginList `json:"PluginList"` + ServerSkill ServerSkill `json:"ServerSkill"` + ServerSkillList ServerSkillList `json:"ServerSkillList"` + SessionAuthStatus SessionAuthStatus `json:"SessionAuthStatus"` + SessionFSAppendFileRequest SessionFSAppendFileRequest `json:"SessionFsAppendFileRequest"` + SessionFSError SessionFSError `json:"SessionFsError"` + SessionFSErrorCode SessionFSErrorCode `json:"SessionFsErrorCode"` + SessionFSExistsRequest SessionFSExistsRequest `json:"SessionFsExistsRequest"` + SessionFSExistsResult SessionFSExistsResult `json:"SessionFsExistsResult"` + SessionFSMkdirRequest SessionFSMkdirRequest `json:"SessionFsMkdirRequest"` + SessionFSReaddirRequest SessionFSReaddirRequest `json:"SessionFsReaddirRequest"` + SessionFSReaddirResult SessionFSReaddirResult `json:"SessionFsReaddirResult"` + SessionFSReaddirWithTypesEntry SessionFSReaddirWithTypesEntry `json:"SessionFsReaddirWithTypesEntry"` + SessionFSReaddirWithTypesEntryType SessionFSReaddirWithTypesEntryType `json:"SessionFsReaddirWithTypesEntryType"` + SessionFSReaddirWithTypesRequest SessionFSReaddirWithTypesRequest `json:"SessionFsReaddirWithTypesRequest"` + SessionFSReaddirWithTypesResult SessionFSReaddirWithTypesResult `json:"SessionFsReaddirWithTypesResult"` + SessionFSReadFileRequest SessionFSReadFileRequest `json:"SessionFsReadFileRequest"` + SessionFSReadFileResult SessionFSReadFileResult `json:"SessionFsReadFileResult"` + SessionFSRenameRequest SessionFSRenameRequest `json:"SessionFsRenameRequest"` + SessionFSRmRequest SessionFSRmRequest `json:"SessionFsRmRequest"` + SessionFSSetProviderConventions SessionFSSetProviderConventions `json:"SessionFsSetProviderConventions"` + SessionFSSetProviderRequest SessionFSSetProviderRequest `json:"SessionFsSetProviderRequest"` + SessionFSSetProviderResult SessionFSSetProviderResult `json:"SessionFsSetProviderResult"` + SessionFSStatRequest SessionFSStatRequest `json:"SessionFsStatRequest"` + SessionFSStatResult SessionFSStatResult `json:"SessionFsStatResult"` + SessionFSWriteFileRequest SessionFSWriteFileRequest `json:"SessionFsWriteFileRequest"` + SessionLogLevel SessionLogLevel `json:"SessionLogLevel"` + SessionMode SessionMode `json:"SessionMode"` + SessionsForkRequest SessionsForkRequest `json:"SessionsForkRequest"` + SessionsForkResult SessionsForkResult `json:"SessionsForkResult"` + ShellExecRequest ShellExecRequest `json:"ShellExecRequest"` + ShellExecResult ShellExecResult `json:"ShellExecResult"` + ShellKillRequest ShellKillRequest `json:"ShellKillRequest"` + ShellKillResult ShellKillResult `json:"ShellKillResult"` + ShellKillSignal ShellKillSignal `json:"ShellKillSignal"` + Skill Skill `json:"Skill"` + SkillList SkillList `json:"SkillList"` + SkillsConfigSetDisabledSkillsRequest SkillsConfigSetDisabledSkillsRequest `json:"SkillsConfigSetDisabledSkillsRequest"` + SkillsConfigSetDisabledSkillsResult SkillsConfigSetDisabledSkillsResult `json:"SkillsConfigSetDisabledSkillsResult"` + SkillsDisableRequest SkillsDisableRequest `json:"SkillsDisableRequest"` + SkillsDisableResult SkillsDisableResult `json:"SkillsDisableResult"` + SkillsDiscoverRequest SkillsDiscoverRequest `json:"SkillsDiscoverRequest"` + SkillsEnableRequest SkillsEnableRequest `json:"SkillsEnableRequest"` + SkillsEnableResult SkillsEnableResult `json:"SkillsEnableResult"` + SkillsReloadResult SkillsReloadResult `json:"SkillsReloadResult"` + TaskAgentInfo TaskAgentInfo `json:"TaskAgentInfo"` + TaskAgentInfoExecutionMode TaskInfoExecutionMode `json:"TaskAgentInfoExecutionMode"` + TaskAgentInfoStatus TaskInfoStatus `json:"TaskAgentInfoStatus"` + TaskInfo TaskInfo `json:"TaskInfo"` + TaskList TaskList `json:"TaskList"` + TasksCancelRequest TasksCancelRequest `json:"TasksCancelRequest"` + TasksCancelResult TasksCancelResult `json:"TasksCancelResult"` + TaskShellInfo TaskShellInfo `json:"TaskShellInfo"` + TaskShellInfoAttachmentMode TaskShellInfoAttachmentMode `json:"TaskShellInfoAttachmentMode"` + TaskShellInfoExecutionMode TaskInfoExecutionMode `json:"TaskShellInfoExecutionMode"` + TaskShellInfoStatus TaskInfoStatus `json:"TaskShellInfoStatus"` + TasksPromoteToBackgroundRequest TasksPromoteToBackgroundRequest `json:"TasksPromoteToBackgroundRequest"` + TasksPromoteToBackgroundResult TasksPromoteToBackgroundResult `json:"TasksPromoteToBackgroundResult"` + TasksRemoveRequest TasksRemoveRequest `json:"TasksRemoveRequest"` + TasksRemoveResult TasksRemoveResult `json:"TasksRemoveResult"` + TasksStartAgentRequest TasksStartAgentRequest `json:"TasksStartAgentRequest"` + TasksStartAgentResult TasksStartAgentResult `json:"TasksStartAgentResult"` + Tool Tool `json:"Tool"` + ToolList ToolList `json:"ToolList"` + ToolsListRequest ToolsListRequest `json:"ToolsListRequest"` + UIElicitationArrayAnyOfField UIElicitationArrayAnyOfField `json:"UIElicitationArrayAnyOfField"` + UIElicitationArrayAnyOfFieldItems UIElicitationArrayAnyOfFieldItems `json:"UIElicitationArrayAnyOfFieldItems"` + UIElicitationArrayAnyOfFieldItemsAnyOf UIElicitationArrayAnyOfFieldItemsAnyOf `json:"UIElicitationArrayAnyOfFieldItemsAnyOf"` + UIElicitationArrayEnumField UIElicitationArrayEnumField `json:"UIElicitationArrayEnumField"` + UIElicitationArrayEnumFieldItems UIElicitationArrayEnumFieldItems `json:"UIElicitationArrayEnumFieldItems"` + UIElicitationFieldValue *UIElicitationFieldValue `json:"UIElicitationFieldValue"` + UIElicitationRequest UIElicitationRequest `json:"UIElicitationRequest"` + UIElicitationResponse UIElicitationResponse `json:"UIElicitationResponse"` + UIElicitationResponseAction UIElicitationResponseAction `json:"UIElicitationResponseAction"` + UIElicitationResponseContent map[string]*UIElicitationFieldValue `json:"UIElicitationResponseContent"` + UIElicitationResult UIElicitationResult `json:"UIElicitationResult"` + UIElicitationSchema UIElicitationSchema `json:"UIElicitationSchema"` + UIElicitationSchemaProperty UIElicitationSchemaProperty `json:"UIElicitationSchemaProperty"` + UIElicitationSchemaPropertyBoolean UIElicitationSchemaPropertyBoolean `json:"UIElicitationSchemaPropertyBoolean"` + UIElicitationSchemaPropertyNumber UIElicitationSchemaPropertyNumber `json:"UIElicitationSchemaPropertyNumber"` + UIElicitationSchemaPropertyNumberType UIElicitationSchemaPropertyNumberTypeEnum `json:"UIElicitationSchemaPropertyNumberType"` + UIElicitationSchemaPropertyString UIElicitationSchemaPropertyString `json:"UIElicitationSchemaPropertyString"` + UIElicitationSchemaPropertyStringFormat UIElicitationSchemaPropertyStringFormat `json:"UIElicitationSchemaPropertyStringFormat"` + UIElicitationStringEnumField UIElicitationStringEnumField `json:"UIElicitationStringEnumField"` + UIElicitationStringOneOfField UIElicitationStringOneOfField `json:"UIElicitationStringOneOfField"` + UIElicitationStringOneOfFieldOneOf UIElicitationStringOneOfFieldOneOf `json:"UIElicitationStringOneOfFieldOneOf"` + UIHandlePendingElicitationRequest UIHandlePendingElicitationRequest `json:"UIHandlePendingElicitationRequest"` + UsageGetMetricsResult UsageGetMetricsResult `json:"UsageGetMetricsResult"` + UsageMetricsCodeChanges UsageMetricsCodeChanges `json:"UsageMetricsCodeChanges"` + UsageMetricsModelMetric UsageMetricsModelMetric `json:"UsageMetricsModelMetric"` + UsageMetricsModelMetricRequests UsageMetricsModelMetricRequests `json:"UsageMetricsModelMetricRequests"` + UsageMetricsModelMetricTokenDetail UsageMetricsModelMetricTokenDetail `json:"UsageMetricsModelMetricTokenDetail"` + UsageMetricsModelMetricUsage UsageMetricsModelMetricUsage `json:"UsageMetricsModelMetricUsage"` + UsageMetricsTokenDetail UsageMetricsTokenDetail `json:"UsageMetricsTokenDetail"` + WorkspacesCreateFileRequest WorkspacesCreateFileRequest `json:"WorkspacesCreateFileRequest"` + WorkspacesCreateFileResult WorkspacesCreateFileResult `json:"WorkspacesCreateFileResult"` + WorkspacesGetWorkspaceResult WorkspacesGetWorkspaceResult `json:"WorkspacesGetWorkspaceResult"` + WorkspacesListFilesResult WorkspacesListFilesResult `json:"WorkspacesListFilesResult"` + WorkspacesReadFileRequest WorkspacesReadFileRequest `json:"WorkspacesReadFileRequest"` + WorkspacesReadFileResult WorkspacesReadFileResult `json:"WorkspacesReadFileResult"` } type AccountGetQuotaRequest struct { @@ -347,6 +362,24 @@ type DiscoveredMCPServer struct { Type *DiscoveredMCPServerType `json:"type,omitempty"` } +type EmbeddedBlobResourceContents struct { + // Base64-encoded binary content of the resource + Blob string `json:"blob"` + // MIME type of the blob content + MIMEType *string `json:"mimeType,omitempty"` + // URI identifying the resource + URI string `json:"uri"` +} + +type EmbeddedTextResourceContents struct { + // MIME type of the text content + MIMEType *string `json:"mimeType,omitempty"` + // Text content of the resource + Text string `json:"text"` + // URI identifying the resource + URI string `json:"uri"` +} + type Extension struct { // Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') ID string `json:"id"` @@ -390,6 +423,168 @@ type ExtensionsEnableResult struct { type ExtensionsReloadResult struct { } +// Expanded external tool result payload +type ExternalToolTextResultForLlm struct { + // Structured content blocks from the tool + Contents []ExternalToolTextResultForLlmContent `json:"contents,omitempty"` + // Optional error message for failed executions + Error *string `json:"error,omitempty"` + // Execution outcome classification. Optional for back-compat; normalized to 'success' (or + // 'failure' when error is present) when missing or unrecognized. + ResultType *string `json:"resultType,omitempty"` + // Detailed log content for timeline display + SessionLog *string `json:"sessionLog,omitempty"` + // Text result returned to the model + TextResultForLlm string `json:"textResultForLlm"` + // Optional tool-specific telemetry + ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` +} + +// A content block within a tool result, which may be text, terminal output, image, audio, +// or a resource +// +// # Plain text content block +// +// Terminal/shell output content block with optional exit code and working directory +// +// # Image content block with base64-encoded data +// +// # Audio content block with base64-encoded data +// +// # Resource link content block referencing an external resource +// +// Embedded resource content block with inline text or binary data +type ExternalToolTextResultForLlmContent struct { + // The text content + // + // Terminal/shell output text + Text *string `json:"text,omitempty"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentType `json:"type"` + // Working directory where the command was executed + Cwd *string `json:"cwd,omitempty"` + // Process exit code, if the command has completed + ExitCode *float64 `json:"exitCode,omitempty"` + // Base64-encoded image data + // + // Base64-encoded audio data + Data *string `json:"data,omitempty"` + // MIME type of the image (e.g., image/png, image/jpeg) + // + // MIME type of the audio (e.g., audio/wav, audio/mpeg) + // + // MIME type of the resource content + MIMEType *string `json:"mimeType,omitempty"` + // Human-readable description of the resource + Description *string `json:"description,omitempty"` + // Icons associated with this resource + Icons []ExternalToolTextResultForLlmContentResourceLinkIcon `json:"icons,omitempty"` + // Resource name identifier + Name *string `json:"name,omitempty"` + // Size of the resource in bytes + Size *float64 `json:"size,omitempty"` + // Human-readable display title for the resource + Title *string `json:"title,omitempty"` + // URI identifying the resource + URI *string `json:"uri,omitempty"` + // The embedded resource contents, either text or base64-encoded binary + Resource *ExternalToolTextResultForLlmContentResourceDetails `json:"resource,omitempty"` +} + +// Icon image for a resource +type ExternalToolTextResultForLlmContentResourceLinkIcon struct { + // MIME type of the icon image + MIMEType *string `json:"mimeType,omitempty"` + // Available icon sizes (e.g., ['16x16', '32x32']) + Sizes []string `json:"sizes,omitempty"` + // URL or path to the icon image + Src string `json:"src"` + // Theme variant this icon is intended for + Theme *ExternalToolTextResultForLlmContentResourceLinkIconTheme `json:"theme,omitempty"` +} + +// The embedded resource contents, either text or base64-encoded binary +type ExternalToolTextResultForLlmContentResourceDetails struct { + // MIME type of the text content + // + // MIME type of the blob content + MIMEType *string `json:"mimeType,omitempty"` + // Text content of the resource + Text *string `json:"text,omitempty"` + // URI identifying the resource + URI string `json:"uri"` + // Base64-encoded binary content of the resource + Blob *string `json:"blob,omitempty"` +} + +// Audio content block with base64-encoded data +type ExternalToolTextResultForLlmContentAudio struct { + // Base64-encoded audio data + Data string `json:"data"` + // MIME type of the audio (e.g., audio/wav, audio/mpeg) + MIMEType string `json:"mimeType"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentAudioType `json:"type"` +} + +// Image content block with base64-encoded data +type ExternalToolTextResultForLlmContentImage struct { + // Base64-encoded image data + Data string `json:"data"` + // MIME type of the image (e.g., image/png, image/jpeg) + MIMEType string `json:"mimeType"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentImageType `json:"type"` +} + +// Embedded resource content block with inline text or binary data +type ExternalToolTextResultForLlmContentResource struct { + // The embedded resource contents, either text or base64-encoded binary + Resource ExternalToolTextResultForLlmContentResourceDetails `json:"resource"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentResourceType `json:"type"` +} + +// Resource link content block referencing an external resource +type ExternalToolTextResultForLlmContentResourceLink struct { + // Human-readable description of the resource + Description *string `json:"description,omitempty"` + // Icons associated with this resource + Icons []ExternalToolTextResultForLlmContentResourceLinkIcon `json:"icons,omitempty"` + // MIME type of the resource content + MIMEType *string `json:"mimeType,omitempty"` + // Resource name identifier + Name string `json:"name"` + // Size of the resource in bytes + Size *float64 `json:"size,omitempty"` + // Human-readable display title for the resource + Title *string `json:"title,omitempty"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentResourceLinkType `json:"type"` + // URI identifying the resource + URI string `json:"uri"` +} + +// Terminal/shell output content block with optional exit code and working directory +type ExternalToolTextResultForLlmContentTerminal struct { + // Working directory where the command was executed + Cwd *string `json:"cwd,omitempty"` + // Process exit code, if the command has completed + ExitCode *float64 `json:"exitCode,omitempty"` + // Terminal/shell output text + Text string `json:"text"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentTerminalType `json:"type"` +} + +// Plain text content block +type ExternalToolTextResultForLlmContentText struct { + // The text content + Text string `json:"text"` + // Content block type discriminator + Type ExternalToolTextResultForLlmContentTextType `json:"type"` +} + // Experimental: FleetStartRequest is part of an experimental API and may change or be removed. type FleetStartRequest struct { // Optional user prompt to combine with fleet instructions @@ -402,7 +597,16 @@ type FleetStartResult struct { Started bool `json:"started"` } -type HandleToolCallResult struct { +type HandlePendingToolCallRequest struct { + // Error message if the tool call failed + Error *string `json:"error,omitempty"` + // Request ID of the pending tool call + RequestID string `json:"requestId"` + // Tool call result (string or expanded result object) + Result *ExternalToolResult `json:"result"` +} + +type HandlePendingToolCallResult struct { // Whether the tool call result was handled successfully Success bool `json:"success"` } @@ -816,6 +1020,8 @@ type PermissionDecision struct { // // Approved and persisted for this project location // + // Approved and persisted across sessions + // // Denied by the user during an interactive prompt // // Denied because user confirmation was unavailable @@ -824,6 +1030,10 @@ type PermissionDecision struct { // // The approval to persist for this location Approval *PermissionDecisionApproveForLocationApproval `json:"approval,omitempty"` + // The URL domain to approve for this session + // + // The URL domain to approve permanently + Domain *string `json:"domain,omitempty"` // The location key (git root or cwd) to persist the approval to LocationKey *string `json:"locationKey,omitempty"` // Optional feedback from the user explaining the denial @@ -882,7 +1092,9 @@ type PermissionDecisionApproveForLocationApprovalWrite struct { type PermissionDecisionApproveForSession struct { // The approval to add as a session-scoped rule - Approval PermissionDecisionApproveForSessionApproval `json:"approval"` + Approval *PermissionDecisionApproveForSessionApproval `json:"approval,omitempty"` + // The URL domain to approve for this session + Domain *string `json:"domain,omitempty"` // Approved and remembered for the rest of the session Kind PermissionDecisionApproveForSessionKind `json:"kind"` } @@ -933,6 +1145,13 @@ type PermissionDecisionApproveOnce struct { Kind PermissionDecisionApproveOnceKind `json:"kind"` } +type PermissionDecisionApprovePermanently struct { + // The URL domain to approve permanently + Domain string `json:"domain"` + // Approved and persisted across sessions + Kind PermissionDecisionApprovePermanentlyKind `json:"kind"` +} + type PermissionDecisionReject struct { // Optional feedback from the user explaining the denial Feedback *string `json:"feedback,omitempty"` @@ -1521,31 +1740,11 @@ type Tool struct { Parameters map[string]any `json:"parameters,omitempty"` } -type ToolCallResult struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Type of the tool result - ResultType *string `json:"resultType,omitempty"` - // Text result to send back to the LLM - TextResultForLlm string `json:"textResultForLlm"` - // Telemetry data from tool execution - ToolTelemetry map[string]any `json:"toolTelemetry,omitempty"` -} - type ToolList struct { // List of available built-in tools with metadata Tools []Tool `json:"tools"` } -type ToolsHandlePendingToolCallRequest struct { - // Error message if the tool call failed - Error *string `json:"error,omitempty"` - // Request ID of the pending tool call - RequestID string `json:"requestId"` - // Tool call result (string or expanded result object) - Result *ToolsHandlePendingToolCall `json:"result"` -} - type ToolsListRequest struct { // Optional model ID — when provided, the returned tool list reflects model-specific // overrides @@ -1710,8 +1909,12 @@ type UsageGetMetricsResult struct { ModelMetrics map[string]UsageMetricsModelMetric `json:"modelMetrics"` // Session start timestamp (epoch milliseconds) SessionStartTime int64 `json:"sessionStartTime"` + // Session-wide per-token-type accumulated token counts + TokenDetails map[string]UsageMetricsTokenDetail `json:"tokenDetails,omitempty"` // Total time spent in model API calls (milliseconds) TotalAPIDurationMS float64 `json:"totalApiDurationMs"` + // Session-wide accumulated nano-AI units cost + TotalNanoAiu *int64 `json:"totalNanoAiu,omitempty"` // Total user-initiated premium request cost across all models (may be fractional due to // multipliers) TotalPremiumRequestCost float64 `json:"totalPremiumRequestCost"` @@ -1732,6 +1935,10 @@ type UsageMetricsCodeChanges struct { type UsageMetricsModelMetric struct { // Request count and cost metrics for this model Requests UsageMetricsModelMetricRequests `json:"requests"` + // Token count details per type + TokenDetails map[string]UsageMetricsModelMetricTokenDetail `json:"tokenDetails,omitempty"` + // Accumulated nano-AI units cost for this model + TotalNanoAiu *int64 `json:"totalNanoAiu,omitempty"` // Token usage metrics for this model Usage UsageMetricsModelMetricUsage `json:"usage"` } @@ -1744,6 +1951,11 @@ type UsageMetricsModelMetricRequests struct { Count int64 `json:"count"` } +type UsageMetricsModelMetricTokenDetail struct { + // Accumulated token count for this token type + TokenCount int64 `json:"tokenCount"` +} + // Token usage metrics for this model type UsageMetricsModelMetricUsage struct { // Total tokens read from prompt cache @@ -1758,6 +1970,11 @@ type UsageMetricsModelMetricUsage struct { ReasoningTokens *int64 `json:"reasoningTokens,omitempty"` } +type UsageMetricsTokenDetail struct { + // Accumulated token count for this token type + TokenCount int64 `json:"tokenCount"` +} + type WorkspacesCreateFileRequest struct { // File content to write as a UTF-8 string Content string `json:"content"` @@ -1862,6 +2079,61 @@ const ( ExtensionStatusStarting ExtensionStatus = "starting" ) +// Theme variant this icon is intended for +type ExternalToolTextResultForLlmContentResourceLinkIconTheme string + +const ( + ExternalToolTextResultForLlmContentResourceLinkIconThemeDark ExternalToolTextResultForLlmContentResourceLinkIconTheme = "dark" + ExternalToolTextResultForLlmContentResourceLinkIconThemeLight ExternalToolTextResultForLlmContentResourceLinkIconTheme = "light" +) + +type ExternalToolTextResultForLlmContentType string + +const ( + ExternalToolTextResultForLlmContentTypeAudio ExternalToolTextResultForLlmContentType = "audio" + ExternalToolTextResultForLlmContentTypeImage ExternalToolTextResultForLlmContentType = "image" + ExternalToolTextResultForLlmContentTypeResource ExternalToolTextResultForLlmContentType = "resource" + ExternalToolTextResultForLlmContentTypeResourceLink ExternalToolTextResultForLlmContentType = "resource_link" + ExternalToolTextResultForLlmContentTypeTerminal ExternalToolTextResultForLlmContentType = "terminal" + ExternalToolTextResultForLlmContentTypeText ExternalToolTextResultForLlmContentType = "text" +) + +type ExternalToolTextResultForLlmContentAudioType string + +const ( + ExternalToolTextResultForLlmContentAudioTypeAudio ExternalToolTextResultForLlmContentAudioType = "audio" +) + +type ExternalToolTextResultForLlmContentImageType string + +const ( + ExternalToolTextResultForLlmContentImageTypeImage ExternalToolTextResultForLlmContentImageType = "image" +) + +type ExternalToolTextResultForLlmContentResourceType string + +const ( + ExternalToolTextResultForLlmContentResourceTypeResource ExternalToolTextResultForLlmContentResourceType = "resource" +) + +type ExternalToolTextResultForLlmContentResourceLinkType string + +const ( + ExternalToolTextResultForLlmContentResourceLinkTypeResourceLink ExternalToolTextResultForLlmContentResourceLinkType = "resource_link" +) + +type ExternalToolTextResultForLlmContentTerminalType string + +const ( + ExternalToolTextResultForLlmContentTerminalTypeTerminal ExternalToolTextResultForLlmContentTerminalType = "terminal" +) + +type ExternalToolTextResultForLlmContentTextType string + +const ( + ExternalToolTextResultForLlmContentTextTypeText ExternalToolTextResultForLlmContentTextType = "text" +) + type FilterMappingString string const ( @@ -1965,6 +2237,7 @@ const ( PermissionDecisionKindApproveForLocation PermissionDecisionKind = "approve-for-location" PermissionDecisionKindApproveForSession PermissionDecisionKind = "approve-for-session" PermissionDecisionKindApproveOnce PermissionDecisionKind = "approve-once" + PermissionDecisionKindApprovePermanently PermissionDecisionKind = "approve-permanently" PermissionDecisionKindReject PermissionDecisionKind = "reject" PermissionDecisionKindUserNotAvailable PermissionDecisionKind = "user-not-available" ) @@ -2029,6 +2302,12 @@ const ( PermissionDecisionApproveOnceKindApproveOnce PermissionDecisionApproveOnceKind = "approve-once" ) +type PermissionDecisionApprovePermanentlyKind string + +const ( + PermissionDecisionApprovePermanentlyKindApprovePermanently PermissionDecisionApprovePermanentlyKind = "approve-permanently" +) + type PermissionDecisionRejectKind string const ( @@ -2197,17 +2476,17 @@ const ( SessionSyncLevelUser SessionSyncLevel = "user" ) +// Tool call result (string or expanded result object) +type ExternalToolResult struct { + ExternalToolTextResultForLlm *ExternalToolTextResultForLlm + String *string +} + type FilterMapping struct { Enum *FilterMappingString EnumMap map[string]FilterMappingString } -// Tool call result (string or expanded result object) -type ToolsHandlePendingToolCall struct { - String *string - ToolCallResult *ToolCallResult -} - type UIElicitationFieldValue struct { Bool *bool Double *float64 @@ -3105,7 +3384,7 @@ func (a *ExtensionsApi) Reload(ctx context.Context) (*ExtensionsReloadResult, er type ToolsApi sessionApi -func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *ToolsHandlePendingToolCallRequest) (*HandleToolCallResult, error) { +func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *HandlePendingToolCallRequest) (*HandlePendingToolCallResult, error) { req := map[string]any{"sessionId": a.sessionID} if params != nil { req["requestId"] = params.RequestID @@ -3120,7 +3399,7 @@ func (a *ToolsApi) HandlePendingToolCall(ctx context.Context, params *ToolsHandl if err != nil { return nil, err } - var result HandleToolCallResult + var result HandlePendingToolCallResult if err := json.Unmarshal(raw, &result); err != nil { return nil, err } diff --git a/go/rpc/result_union.go b/go/rpc/result_union.go index aabfe6553..3387dce1b 100644 --- a/go/rpc/result_union.go +++ b/go/rpc/result_union.go @@ -2,33 +2,33 @@ package rpc import "encoding/json" -// MarshalJSON serializes ToolsHandlePendingToolCall as the appropriate JSON variant: -// a plain string when String is set, or the ToolCallResult object otherwise. +// MarshalJSON serializes ExternalToolResult as the appropriate JSON variant: +// a plain string when String is set, or the ExternalToolTextResultForLlm object otherwise. // The generated struct has no custom marshaler, so without this the Go -// struct fields would serialize as {"ToolCallResult":...,"String":...} +// struct fields would serialize as {"ExternalToolTextResultForLlm":...,"String":...} // instead of the union the server expects. -func (r ToolsHandlePendingToolCall) MarshalJSON() ([]byte, error) { +func (r ExternalToolResult) MarshalJSON() ([]byte, error) { if r.String != nil { return json.Marshal(*r.String) } - if r.ToolCallResult != nil { - return json.Marshal(*r.ToolCallResult) + if r.ExternalToolTextResultForLlm != nil { + return json.Marshal(*r.ExternalToolTextResultForLlm) } return []byte("null"), nil } -// UnmarshalJSON deserializes a JSON value into the appropriate ToolsHandlePendingToolCall variant. -func (r *ToolsHandlePendingToolCall) UnmarshalJSON(data []byte) error { +// UnmarshalJSON deserializes a JSON value into the appropriate ExternalToolResult variant. +func (r *ExternalToolResult) UnmarshalJSON(data []byte) error { // Try string first var s string if err := json.Unmarshal(data, &s); err == nil { r.String = &s return nil } - // Try ToolCallResult object - var rr ToolCallResult + // Try ExternalToolTextResultForLlm object + var rr ExternalToolTextResultForLlm if err := json.Unmarshal(data, &rr); err == nil { - r.ToolCallResult = &rr + r.ExternalToolTextResultForLlm = &rr return nil } return nil diff --git a/go/session.go b/go/session.go index 8bcdf57c8..b58972c15 100644 --- a/go/session.go +++ b/go/session.go @@ -966,7 +966,7 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, defer func() { if r := recover(); r != nil { errMsg := fmt.Sprintf("tool panic: %v", r) - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{ RequestID: requestID, Error: &errMsg, }) @@ -984,7 +984,7 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, result, err := handler(invocation) if err != nil { errMsg := err.Error() - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{ RequestID: requestID, Error: &errMsg, }) @@ -1006,17 +1006,17 @@ func (s *Session) executeToolAndRespond(requestID, toolName, toolCallID string, } } - rpcResult := rpc.ToolsHandlePendingToolCall{ - ToolCallResult: &rpc.ToolCallResult{ + rpcResult := rpc.ExternalToolResult{ + ExternalToolTextResultForLlm: &rpc.ExternalToolTextResultForLlm{ TextResultForLlm: textResultForLLM, ToolTelemetry: result.ToolTelemetry, ResultType: &effectiveResultType, }, } if result.Error != "" { - rpcResult.ToolCallResult.Error = &result.Error + rpcResult.ExternalToolTextResultForLlm.Error = &result.Error } - s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.ToolsHandlePendingToolCallRequest{ + s.RPC.Tools.HandlePendingToolCall(ctx, &rpc.HandlePendingToolCallRequest{ RequestID: requestID, Result: &rpcResult, }) diff --git a/go/types.go b/go/types.go index 9b971aad4..e43bf2ed2 100644 --- a/go/types.go +++ b/go/types.go @@ -804,6 +804,15 @@ type ResumeSessionConfig struct { // DisableResume, when true, skips emitting the session.resume event. // Useful for reconnecting to a session without triggering resume-related side effects. DisableResume bool + // ContinuePendingWork, when true, instructs the runtime to continue any tool calls + // or permission prompts that were still pending when the session was last suspended. + // When false (the default), the runtime treats pending work as interrupted on resume. + // + // For permission requests, the runtime re-emits permission.requested so the + // registered OnPermissionRequest handler can re-prompt; for external tool calls, + // the consumer is expected to supply the result via the corresponding low-level + // RPC method. + ContinuePendingWork bool // OnEvent is an optional event handler registered before the session.resume RPC // is issued, ensuring early events are delivered. See SessionConfig.OnEvent. OnEvent SessionEventHandler @@ -1050,6 +1059,7 @@ type resumeSessionRequest struct { ConfigDir string `json:"configDir,omitempty"` EnableConfigDiscovery *bool `json:"enableConfigDiscovery,omitempty"` DisableResume *bool `json:"disableResume,omitempty"` + ContinuePendingWork *bool `json:"continuePendingWork,omitempty"` Streaming *bool `json:"streaming,omitempty"` IncludeSubAgentStreamingEvents *bool `json:"includeSubAgentStreamingEvents,omitempty"` MCPServers map[string]MCPServerConfig `json:"mcpServers,omitempty"` diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json index 0988fe241..8f8f12a00 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.40-0", + "@github/copilot": "^1.0.40-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -663,26 +663,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.40-0.tgz", - "integrity": "sha512-KIrRqPOCGPcYUq09wvi66qUi5YMFTH5z9fOEzo1BuyLFVQqUen0TtRk0mpbhG6TxArLPqosBY8nDXOdc+NqKKw==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.40-1.tgz", + "integrity": "sha512-jdohe7CcjlmbNR0bnL/uAbaYDtmqZnHcHo3t/ZspVb9vo7+yk7AdTc4AF4TpV7M/tCKc+ynoqgQNCalWAmXYWQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.40-0", - "@github/copilot-darwin-x64": "1.0.40-0", - "@github/copilot-linux-arm64": "1.0.40-0", - "@github/copilot-linux-x64": "1.0.40-0", - "@github/copilot-win32-arm64": "1.0.40-0", - "@github/copilot-win32-x64": "1.0.40-0" + "@github/copilot-darwin-arm64": "1.0.40-1", + "@github/copilot-darwin-x64": "1.0.40-1", + "@github/copilot-linux-arm64": "1.0.40-1", + "@github/copilot-linux-x64": "1.0.40-1", + "@github/copilot-win32-arm64": "1.0.40-1", + "@github/copilot-win32-x64": "1.0.40-1" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.40-0.tgz", - "integrity": "sha512-l905DiMvOB7Jn5MAkS+iEQcM99hmJHQ5yrKaGJ9OteVWBCcxPEO5ctnRYFdeq4NHL+9OnAzJzNc0kwScOAUCzg==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.40-1.tgz", + "integrity": "sha512-DSArBmv1A6BstJs23QgXzes7B8Hu+sz4Ccqh1NhY/4NPfxck1rD2v61ZWz8kdwpgTdjP6lWSZ5nLWdi9Go+PhA==", "cpu": [ "arm64" ], @@ -696,9 +696,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.40-0.tgz", - "integrity": "sha512-e/Dtc1CjZ5SpNLdtY7vHxzH3hhNKfiMJdyp/2UY/6rzXsSPfbw9xZL01nUBbZt0aYj2FbPBDMTyfqoh5weUSww==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.40-1.tgz", + "integrity": "sha512-fTOL2XChDSsPc/q//mIFlq47ABNgyUKqz1+ix5oxloE9RlT1YpD7v3O+dqU/tmdxXwMTfQqgZsYglRY/nna3yw==", "cpu": [ "x64" ], @@ -712,9 +712,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.40-0.tgz", - "integrity": "sha512-fR/gVUXFpjwojNf3Pg5lH2vrb8q0Gghv6RK6xx1QS6pEBdPTMGeoPxQVqSSB6dZCR/X3dkK1tIZ5IGnuABDHbA==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.40-1.tgz", + "integrity": "sha512-+aon/9kAxgGlxLZrxzwUrNO1G/eUIhhEB6W4u1sMs2GwXjANrDr6WIsp056dOnU2TY9oEnn8vWTJQSIwyFlGHQ==", "cpu": [ "arm64" ], @@ -728,9 +728,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.40-0.tgz", - "integrity": "sha512-vDfE5Cxgg+BealbxBjqa0MUrLGJF+zT+XygMFgWXi/4ESVpRvvAet8rUoLeL+7Lgg6QRlS+UMPWfWL6ZAoSp5w==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.40-1.tgz", + "integrity": "sha512-p44TeuI01EpizsJmQX0W21f7pPXyTvrXYkv/5W1GgCiNEP4HKLG9rdgdIEtqaIMnXFKssxpGF45+hJuz0iep5g==", "cpu": [ "x64" ], @@ -744,9 +744,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.40-0.tgz", - "integrity": "sha512-7aEo1CZ5476lWRtfcym0TX1DhH5l9+PKENHPOr8vfziHKeNlWYkXHVTo7OGIUnCAwIetbCQRyf92nnaFhG/8Jw==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.40-1.tgz", + "integrity": "sha512-nrbMh1qc8VWL1KU2N+VdQ2M+4jiGlugApIFNUbfywnUITktWEpQ62Jqf2YQlrMr51f1F1fjSE/YBuDW6InzzYg==", "cpu": [ "arm64" ], @@ -760,9 +760,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.40-0.tgz", - "integrity": "sha512-egnoilEO6TS0qSexe+A26GUSyfeinaqXJRTYL4Qr6eVgAfFhCEpCJtK/gHIZmlDXnq2ex3jGrVvz9+ZIp+JSoA==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.40-1.tgz", + "integrity": "sha512-n5qmzAn4OKbdHJeOmdt4eUA7XWSUOmNSJUtRswKVqgCxrmMOR/0FXCPzVS0gAJn+UEj9ApHulC6G09HR5Nhp7Q==", "cpu": [ "x64" ], diff --git a/nodejs/package.json b/nodejs/package.json index 6e53791b9..b04d19511 100644 --- a/nodejs/package.json +++ b/nodejs/package.json @@ -56,7 +56,7 @@ "author": "GitHub", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.40-0", + "@github/copilot": "^1.0.40-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json index 4d9076e84..dfc68c929 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.40-0", + "@github/copilot": "^1.0.40-1", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, diff --git a/nodejs/src/client.ts b/nodejs/src/client.ts index 5eadf58dd..931ce59a4 100644 --- a/nodejs/src/client.ts +++ b/nodejs/src/client.ts @@ -907,6 +907,7 @@ export class CopilotClient { disabledSkills: config.disabledSkills, infiniteSessions: config.infiniteSessions, disableResume: config.disableResume, + continuePendingWork: config.continuePendingWork, gitHubToken: config.gitHubToken, }); diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts index d792e2aca..349201951 100644 --- a/nodejs/src/generated/rpc.ts +++ b/nodejs/src/generated/rpc.ts @@ -40,6 +40,42 @@ export type ExtensionSource = "project" | "user"; * via the `definition` "ExtensionStatus". */ export type ExtensionStatus = "running" | "disabled" | "failed" | "starting"; +/** + * Tool call result (string or expanded result object) + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolResult". + */ +export type ExternalToolResult = string | ExternalToolTextResultForLlm; +/** + * A content block within a tool result, which may be text, terminal output, image, audio, or a resource + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContent". + */ +export type ExternalToolTextResultForLlmContent = + | ExternalToolTextResultForLlmContentText + | ExternalToolTextResultForLlmContentTerminal + | ExternalToolTextResultForLlmContentImage + | ExternalToolTextResultForLlmContentAudio + | ExternalToolTextResultForLlmContentResourceLink + | ExternalToolTextResultForLlmContentResource; +/** + * Theme variant this icon is intended for + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentResourceLinkIconTheme". + */ +export type ExternalToolTextResultForLlmContentResourceLinkIconTheme = "light" | "dark"; +/** + * The embedded resource contents, either text or base64-encoded binary + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentResourceDetails". + */ +export type ExternalToolTextResultForLlmContentResourceDetails = + | EmbeddedTextResourceContents + | EmbeddedBlobResourceContents; export type FilterMapping = | { @@ -113,6 +149,7 @@ export type PermissionDecision = | PermissionDecisionApproveOnce | PermissionDecisionApproveForSession | PermissionDecisionApproveForLocation + | PermissionDecisionApprovePermanently | PermissionDecisionReject | PermissionDecisionUserNotAvailable; /** @@ -208,13 +245,6 @@ export type TaskShellInfoAttachmentMode = "attached" | "detached"; * via the `definition` "TaskShellInfoExecutionMode". */ export type TaskShellInfoExecutionMode = "sync" | "background"; -/** - * Tool call result (string or expanded result object) - * - * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema - * via the `definition` "ToolsHandlePendingToolCall". - */ -export type ToolsHandlePendingToolCall = string | ToolCallResult; export type UIElicitationFieldValue = string | number | boolean | string[]; @@ -383,6 +413,36 @@ export interface DiscoveredMcpServer { enabled: boolean; } +export interface EmbeddedBlobResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the blob content + */ + mimeType?: string; + /** + * Base64-encoded binary content of the resource + */ + blob: string; +} + +export interface EmbeddedTextResourceContents { + /** + * URI identifying the resource + */ + uri: string; + /** + * MIME type of the text content + */ + mimeType?: string; + /** + * Text content of the resource + */ + text: string; +} + export interface Extension { /** * Source-qualified ID (e.g., 'project:my-ext', 'user:auth-helper') @@ -423,6 +483,195 @@ export interface ExtensionsEnableRequest { */ id: string; } +/** + * Expanded external tool result payload + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlm". + */ +export interface ExternalToolTextResultForLlm { + /** + * Text result returned to the model + */ + textResultForLlm: string; + /** + * Execution outcome classification. Optional for back-compat; normalized to 'success' (or 'failure' when error is present) when missing or unrecognized. + */ + resultType?: string; + /** + * Optional error message for failed executions + */ + error?: string; + /** + * Detailed log content for timeline display + */ + sessionLog?: string; + /** + * Optional tool-specific telemetry + */ + toolTelemetry?: { + [k: string]: unknown; + }; + /** + * Structured content blocks from the tool + */ + contents?: ExternalToolTextResultForLlmContent[]; + [k: string]: unknown; +} +/** + * Plain text content block + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentText". + */ +export interface ExternalToolTextResultForLlmContentText { + /** + * Content block type discriminator + */ + type: "text"; + /** + * The text content + */ + text: string; +} +/** + * Terminal/shell output content block with optional exit code and working directory + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentTerminal". + */ +export interface ExternalToolTextResultForLlmContentTerminal { + /** + * Content block type discriminator + */ + type: "terminal"; + /** + * Terminal/shell output text + */ + text: string; + /** + * Process exit code, if the command has completed + */ + exitCode?: number; + /** + * Working directory where the command was executed + */ + cwd?: string; +} +/** + * Image content block with base64-encoded data + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentImage". + */ +export interface ExternalToolTextResultForLlmContentImage { + /** + * Content block type discriminator + */ + type: "image"; + /** + * Base64-encoded image data + */ + data: string; + /** + * MIME type of the image (e.g., image/png, image/jpeg) + */ + mimeType: string; +} +/** + * Audio content block with base64-encoded data + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentAudio". + */ +export interface ExternalToolTextResultForLlmContentAudio { + /** + * Content block type discriminator + */ + type: "audio"; + /** + * Base64-encoded audio data + */ + data: string; + /** + * MIME type of the audio (e.g., audio/wav, audio/mpeg) + */ + mimeType: string; +} +/** + * Resource link content block referencing an external resource + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentResourceLink". + */ +export interface ExternalToolTextResultForLlmContentResourceLink { + /** + * Icons associated with this resource + */ + icons?: ExternalToolTextResultForLlmContentResourceLinkIcon[]; + /** + * Resource name identifier + */ + name: string; + /** + * Human-readable display title for the resource + */ + title?: string; + /** + * URI identifying the resource + */ + uri: string; + /** + * Human-readable description of the resource + */ + description?: string; + /** + * MIME type of the resource content + */ + mimeType?: string; + /** + * Size of the resource in bytes + */ + size?: number; + /** + * Content block type discriminator + */ + type: "resource_link"; +} +/** + * Icon image for a resource + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentResourceLinkIcon". + */ +export interface ExternalToolTextResultForLlmContentResourceLinkIcon { + /** + * URL or path to the icon image + */ + src: string; + /** + * MIME type of the icon image + */ + mimeType?: string; + /** + * Available icon sizes (e.g., ['16x16', '32x32']) + */ + sizes?: string[]; + theme?: ExternalToolTextResultForLlmContentResourceLinkIconTheme; +} +/** + * Embedded resource content block with inline text or binary data + * + * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema + * via the `definition` "ExternalToolTextResultForLlmContentResource". + */ +export interface ExternalToolTextResultForLlmContentResource { + /** + * Content block type discriminator + */ + type: "resource"; + resource: ExternalToolTextResultForLlmContentResourceDetails; +} /** @experimental */ export interface FleetStartRequest { @@ -440,7 +689,19 @@ export interface FleetStartResult { started: boolean; } -export interface HandleToolCallResult { +export interface HandlePendingToolCallRequest { + /** + * Request ID of the pending tool call + */ + requestId: string; + result?: ExternalToolResult; + /** + * Error message if the tool call failed + */ + error?: string; +} + +export interface HandlePendingToolCallResult { /** * Whether the tool call result was handled successfully */ @@ -966,7 +1227,11 @@ export interface PermissionDecisionApproveForSession { * Approved and remembered for the rest of the session */ kind: "approve-for-session"; - approval: PermissionDecisionApproveForSessionApproval; + approval?: PermissionDecisionApproveForSessionApproval; + /** + * The URL domain to approve for this session + */ + domain?: string; } export interface PermissionDecisionApproveForSessionApprovalCommands { @@ -1047,6 +1312,17 @@ export interface PermissionDecisionApproveForLocationApprovalCustomTool { toolName: string; } +export interface PermissionDecisionApprovePermanently { + /** + * Approved and persisted across sessions + */ + kind: "approve-permanently"; + /** + * The URL domain to approve permanently + */ + domain: string; +} + export interface PermissionDecisionReject { /** * Denied by the user during an interactive prompt @@ -1827,27 +2103,6 @@ export interface Tool { instructions?: string; } -export interface ToolCallResult { - /** - * Text result to send back to the LLM - */ - textResultForLlm: string; - /** - * Type of the tool result - */ - resultType?: string; - /** - * Error message if the tool call failed - */ - error?: string; - /** - * Telemetry data from tool execution - */ - toolTelemetry?: { - [k: string]: unknown; - }; -} - export interface ToolList { /** * List of available built-in tools with metadata @@ -1855,18 +2110,6 @@ export interface ToolList { tools: Tool[]; } -export interface ToolsHandlePendingToolCallRequest { - /** - * Request ID of the pending tool call - */ - requestId: string; - result?: ToolsHandlePendingToolCall; - /** - * Error message if the tool call failed - */ - error?: string; -} - export interface ToolsListRequest { /** * Optional model ID — when provided, the returned tool list reflects model-specific overrides @@ -2030,6 +2273,16 @@ export interface UsageGetMetricsResult { * Raw count of user-initiated API requests */ totalUserRequests: number; + /** + * Session-wide accumulated nano-AI units cost + */ + totalNanoAiu?: number; + /** + * Session-wide per-token-type accumulated token counts + */ + tokenDetails?: { + [k: string]: UsageMetricsTokenDetail; + }; /** * Total time spent in model API calls (milliseconds) */ @@ -2058,6 +2311,13 @@ export interface UsageGetMetricsResult { */ lastCallOutputTokens: number; } + +export interface UsageMetricsTokenDetail { + /** + * Accumulated token count for this token type + */ + tokenCount: number; +} /** * Aggregated code change metrics * @@ -2082,6 +2342,16 @@ export interface UsageMetricsCodeChanges { export interface UsageMetricsModelMetric { requests: UsageMetricsModelMetricRequests; usage: UsageMetricsModelMetricUsage; + /** + * Accumulated nano-AI units cost for this model + */ + totalNanoAiu?: number; + /** + * Token count details per type + */ + tokenDetails?: { + [k: string]: UsageMetricsModelMetricTokenDetail; + }; } /** * Request count and cost metrics for this model @@ -2128,6 +2398,13 @@ export interface UsageMetricsModelMetricUsage { reasoningTokens?: number; } +export interface UsageMetricsModelMetricTokenDetail { + /** + * Accumulated token count for this token type + */ + tokenCount: number; +} + export interface WorkspacesCreateFileRequest { /** * Relative path within the workspace files directory @@ -2363,7 +2640,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin connection.sendRequest("session.extensions.reload", { sessionId }), }, tools: { - handlePendingToolCall: async (params: ToolsHandlePendingToolCallRequest): Promise => + handlePendingToolCall: async (params: HandlePendingToolCallRequest): Promise => connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params }), }, commands: { diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts index dc919cb46..23c61710f 100644 --- a/nodejs/src/generated/session-events.ts +++ b/nodejs/src/generated/session-events.ts @@ -208,17 +208,28 @@ export type PermissionPromptRequestMemoryDirection = "upvote" | "downvote"; */ export type PermissionPromptRequestPathAccessKind = "read" | "shell" | "write"; /** - * The outcome of the permission request - */ -export type PermissionCompletedKind = - | "approved" - | "approved-for-session" - | "approved-for-location" - | "denied-by-rules" - | "denied-no-approval-rule-and-could-not-request-from-user" - | "denied-interactively-by-user" - | "denied-by-content-exclusion-policy" - | "denied-by-permission-request-hook"; + * The result of the permission request + */ +export type PermissionResult = + | PermissionApproved + | PermissionApprovedForSession + | PermissionApprovedForLocation + | PermissionCancelled + | PermissionDeniedByRules + | PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser + | PermissionDeniedInteractivelyByUser + | PermissionDeniedByContentExclusionPolicy + | PermissionDeniedByPermissionRequestHook; +/** + * The approval to add as a session-scoped rule + */ +export type UserToolSessionApproval = + | UserToolSessionApprovalCommands + | UserToolSessionApprovalRead + | UserToolSessionApprovalWrite + | UserToolSessionApprovalMcp + | UserToolSessionApprovalMemory + | UserToolSessionApprovalCustomTool; /** * Elicitation mode; "form" for structured input, "url" for browser-based. Defaults to "form" when absent. */ @@ -390,6 +401,10 @@ export interface ResumeData { */ alreadyInUse?: boolean; context?: WorkingDirectoryContext; + /** + * When true, tool calls and permission requests left in flight by the previous session lifetime remain pending after resume and the agentic loop awaits their results. User sends are queued behind the pending work until all such requests reach a terminal state. When false (the default), any such tool calls and permission requests are immediately marked as interrupted on resume. + */ + continuePendingWork?: boolean; /** * Total number of persisted events in the session at the time of resume */ @@ -410,6 +425,10 @@ export interface ResumeData { * Model currently selected at resume time */ selectedModel?: string; + /** + * True when this resume attached to a session that the runtime already had running in-memory (for example, an extension joining a session another client was actively driving). False (or omitted) for cold resumes — the runtime had to reconstitute the session from its persisted event log. + */ + sessionWasActive?: boolean; } export interface RemoteSteerableChangedEvent { /** @@ -1024,6 +1043,12 @@ export interface ShutdownData { * System message token count at shutdown */ systemTokens?: number; + /** + * Session-wide per-token-type accumulated token counts + */ + tokenDetails?: { + [k: string]: ShutdownTokenDetail; + }; /** * Tool definitions token count at shutdown */ @@ -1032,6 +1057,10 @@ export interface ShutdownData { * Cumulative time spent in API calls during the session, in milliseconds */ totalApiDurationMs: number; + /** + * Session-wide accumulated nano-AI units cost + */ + totalNanoAiu?: number; /** * Total number of premium API requests used during the session */ @@ -1056,6 +1085,16 @@ export interface ShutdownCodeChanges { } export interface ShutdownModelMetric { requests: ShutdownModelMetricRequests; + /** + * Token count details per type + */ + tokenDetails?: { + [k: string]: ShutdownModelMetricTokenDetail; + }; + /** + * Accumulated nano-AI units cost for this model + */ + totalNanoAiu?: number; usage: ShutdownModelMetricUsage; } /** @@ -1071,6 +1110,12 @@ export interface ShutdownModelMetricRequests { */ count: number; } +export interface ShutdownModelMetricTokenDetail { + /** + * Accumulated token count for this token type + */ + tokenCount: number; +} /** * Token usage breakdown */ @@ -1096,6 +1141,12 @@ export interface ShutdownModelMetricUsage { */ reasoningTokens?: number; } +export interface ShutdownTokenDetail { + /** + * Accumulated token count for this token type + */ + tokenCount: number; +} export interface ContextChangedEvent { /** * Sub-agent instance identifier. Absent for events from the root/main agent and session-level events. @@ -1340,7 +1391,7 @@ export interface CompactionCompleteCompactionTokensUsedCopilotUsage { */ tokenDetails: CompactionCompleteCompactionTokensUsedCopilotUsageTokenDetail[]; /** - * Total cost in nano-AIU (AI Units) for this request + * Total cost in nano-AI units for this request */ totalNanoAiu: number; } @@ -1444,6 +1495,10 @@ export interface UserMessageData { * Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit */ nativeDocumentPathFallbackPaths?: string[]; + /** + * Parent agent task ID for background telemetry correlated to this user turn + */ + parentAgentTaskId?: string; /** * Origin of this message, used for timeline filtering (e.g., "skill-pdf" for skill-injected messages that should be hidden from the user) */ @@ -1873,6 +1928,10 @@ export interface AssistantMessageData { * Tool invocations requested by the assistant in this message */ toolRequests?: AssistantMessageToolRequest[]; + /** + * Identifier for the agent loop turn that produced this message, matching the corresponding assistant.turn_start event + */ + turnId?: string; } /** * A tool invocation request from the assistant @@ -2081,7 +2140,7 @@ export interface AssistantUsageCopilotUsage { */ tokenDetails: AssistantUsageCopilotUsageTokenDetail[]; /** - * Total cost in nano-AIU (AI Units) for this request + * Total cost in nano-AI units for this request */ totalNanoAiu: number; } @@ -2326,6 +2385,10 @@ export interface ToolExecutionStartData { * Name of the tool being executed */ toolName: string; + /** + * Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event + */ + turnId?: string; } export interface ToolExecutionPartialResultEvent { /** @@ -2456,6 +2519,10 @@ export interface ToolExecutionCompleteData { toolTelemetry?: { [k: string]: unknown; }; + /** + * Identifier for the agent loop turn this tool was invoked in, matching the corresponding assistant.turn_start event + */ + turnId?: string; } /** * Error details when the tool execution failed @@ -3234,7 +3301,10 @@ export interface PermissionRequestedEvent { */ agentId?: string; data: PermissionRequestedData; - ephemeral: true; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -3763,7 +3833,10 @@ export interface PermissionCompletedEvent { */ agentId?: string; data: PermissionCompletedData; - ephemeral: true; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -3786,17 +3859,165 @@ export interface PermissionCompletedData { * Request ID of the resolved permission request; clients should dismiss any UI for this request */ requestId: string; - result: PermissionCompletedResult; + result: PermissionResult; /** * Optional tool call ID associated with this permission prompt; clients may use it to correlate UI created from tool-scoped prompts */ toolCallId?: string; } -/** - * The result of the permission request - */ -export interface PermissionCompletedResult { - kind: PermissionCompletedKind; +export interface PermissionApproved { + /** + * The permission request was approved + */ + kind: "approved"; +} +export interface PermissionApprovedForSession { + approval: UserToolSessionApproval; + /** + * Approved and remembered for the rest of the session + */ + kind: "approved-for-session"; +} +export interface UserToolSessionApprovalCommands { + /** + * Command identifiers approved by the user + */ + commandIdentifiers: string[]; + /** + * Command approval kind + */ + kind: "commands"; +} +export interface UserToolSessionApprovalRead { + /** + * Read approval kind + */ + kind: "read"; +} +export interface UserToolSessionApprovalWrite { + /** + * Write approval kind + */ + kind: "write"; +} +export interface UserToolSessionApprovalMcp { + /** + * MCP tool approval kind + */ + kind: "mcp"; + /** + * MCP server name + */ + serverName: string; + /** + * Optional MCP tool name, or null for all tools on the server + */ + toolName: string | null; +} +export interface UserToolSessionApprovalMemory { + /** + * Memory approval kind + */ + kind: "memory"; +} +export interface UserToolSessionApprovalCustomTool { + /** + * Custom tool approval kind + */ + kind: "custom-tool"; + /** + * Custom tool name + */ + toolName: string; +} +export interface PermissionApprovedForLocation { + approval: UserToolSessionApproval; + /** + * Approved and persisted for this project location + */ + kind: "approved-for-location"; + /** + * The location key (git root or cwd) to persist the approval to + */ + locationKey: string; +} +export interface PermissionCancelled { + /** + * The permission request was cancelled before a response was used + */ + kind: "cancelled"; + /** + * Optional explanation of why the request was cancelled + */ + reason?: string; +} +export interface PermissionDeniedByRules { + /** + * Denied because approval rules explicitly blocked it + */ + kind: "denied-by-rules"; + /** + * Rules that denied the request + */ + rules: PermissionRule[]; +} +export interface PermissionRule { + /** + * Optional rule argument matched against the request + */ + argument: string | null; + /** + * The rule kind, such as Shell or GitHubMCP + */ + kind: string; +} +export interface PermissionDeniedNoApprovalRuleAndCouldNotRequestFromUser { + /** + * Denied because no approval rule matched and user confirmation was unavailable + */ + kind: "denied-no-approval-rule-and-could-not-request-from-user"; +} +export interface PermissionDeniedInteractivelyByUser { + /** + * Optional feedback from the user explaining the denial + */ + feedback?: string; + /** + * Whether to force-reject the current agent turn + */ + forceReject?: boolean; + /** + * Denied by the user during an interactive prompt + */ + kind: "denied-interactively-by-user"; +} +export interface PermissionDeniedByContentExclusionPolicy { + /** + * Denied by the organization's content exclusion policy + */ + kind: "denied-by-content-exclusion-policy"; + /** + * Human-readable explanation of why the path was excluded + */ + message: string; + /** + * File path that triggered the exclusion + */ + path: string; +} +export interface PermissionDeniedByPermissionRequestHook { + /** + * Whether to interrupt the current agent turn + */ + interrupt?: boolean; + /** + * Denied by a permission request hook registered by an extension or plugin + */ + kind: "denied-by-permission-request-hook"; + /** + * Optional message from the hook explaining the denial + */ + message?: string; } export interface UserInputRequestedEvent { /** @@ -4144,7 +4365,10 @@ export interface ExternalToolRequestedEvent { */ agentId?: string; data: ExternalToolRequestedData; - ephemeral: true; + /** + * When true, the event is transient and not persisted to the session event log on disk + */ + ephemeral?: boolean; /** * Unique event identifier (UUID v4), generated when the event is emitted */ @@ -4200,7 +4424,7 @@ export interface ExternalToolCompletedEvent { */ agentId?: string; data: ExternalToolCompletedData; - ephemeral: true; + ephemeral?: true; /** * Unique event identifier (UUID v4), generated when the event is emitted */ diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 335571b50..93f2360fa 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -1421,6 +1421,18 @@ export type ResumeSessionConfig = Pick< * @default false */ disableResume?: boolean; + /** + * When true, the runtime continues any tool calls or permission prompts that were + * still pending when the session was last suspended. When false (the default), the + * runtime treats pending work as interrupted on resume. + * + * For permission requests, the runtime re-emits `permission.requested` so the + * registered `onPermissionRequest` handler can re-prompt; for external tool calls, + * the consumer is expected to supply the result via the corresponding low-level + * RPC method. + * @default false + */ + continuePendingWork?: boolean; }; /** diff --git a/nodejs/test/client.test.ts b/nodejs/test/client.test.ts index 39d6dfa76..3c5217961 100644 --- a/nodejs/test/client.test.ts +++ b/nodejs/test/client.test.ts @@ -166,6 +166,47 @@ describe("CopilotClient", () => { spy.mockRestore(); }); + it("forwards continuePendingWork in session.resume request", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession(session.sessionId, { + onPermissionRequest: approveAll, + continuePendingWork: true, + }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any; + expect(payload.continuePendingWork).toBe(true); + spy.mockRestore(); + }); + + it("omits continuePendingWork from session.resume payload when not specified", async () => { + const client = new CopilotClient(); + await client.start(); + onTestFinished(() => client.forceStop()); + + const session = await client.createSession({ onPermissionRequest: approveAll }); + const spy = vi + .spyOn((client as any).connection!, "sendRequest") + .mockImplementation(async (method: string, params: any) => { + if (method === "session.resume") return { sessionId: params.sessionId }; + throw new Error(`Unexpected method: ${method}`); + }); + await client.resumeSession(session.sessionId, { onPermissionRequest: approveAll }); + + const payload = spy.mock.calls.find((c) => c[0] === "session.resume")![1] as any; + expect(payload.continuePendingWork).toBeUndefined(); + spy.mockRestore(); + }); + it("forwards provider headers in session.create request", async () => { const client = new CopilotClient(); await client.start(); diff --git a/nodejs/test/e2e/harness/sdkTestContext.ts b/nodejs/test/e2e/harness/sdkTestContext.ts index f17419295..474a4e0f4 100644 --- a/nodejs/test/e2e/harness/sdkTestContext.ts +++ b/nodejs/test/e2e/harness/sdkTestContext.ts @@ -11,7 +11,7 @@ import { fileURLToPath } from "url"; import { afterAll, afterEach, beforeEach, onTestFailed, TestContext } from "vitest"; import { CopilotClient, CopilotClientOptions } from "../../../src"; import { CapiProxy } from "./CapiProxy"; -import { retry } from "./sdkTestHelper"; +import { retry, formatError } from "./sdkTestHelper"; export const isCI = process.env.GITHUB_ACTIONS === "true"; @@ -111,6 +111,17 @@ function getTrafficCapturePath(testContext: TestContext): string { return join(SNAPSHOTS_DIR, testFileName, `${taskNameAsFilename}.yaml`); } -function rmDir(message: string, path: string): Promise { - return retry(message, () => rm(path, { recursive: true, force: true }), 5, 2000); +async function rmDir(message: string, path: string): Promise { + // Use longer retries to tolerate Windows holding SQLite session-store.db + // open briefly after the CLI subprocess exits. If the temp dir still can't + // be removed (e.g. CLI background writer racing with cleanup), warn and + // continue rather than failing the whole test run — the OS / CI runner + // will reclaim the temp dir on shutdown. + try { + await retry(message, () => rm(path, { recursive: true, force: true }), 30, 1000); + } catch (error) { + console.warn( + `WARN: ${message} failed; leaving temp dir for OS cleanup: ${formatError(error)}` + ); + } } diff --git a/python/copilot/client.py b/python/copilot/client.py index c800e2158..40ea71b83 100644 --- a/python/copilot/client.py +++ b/python/copilot/client.py @@ -1513,6 +1513,7 @@ async def resume_session( on_elicitation_request: ElicitationHandler | None = None, create_session_fs_handler: CreateSessionFsHandler | None = None, github_token: str | None = None, + continue_pending_work: bool | None = None, ) -> CopilotSession: """ Resume an existing conversation session by its ID. @@ -1560,6 +1561,10 @@ async def resume_session( disabled_skills: Skills to disable. infinite_sessions: Infinite session configuration. on_event: Callback for session events. + continue_pending_work: When True, instructs the runtime to continue any + tool calls or permission prompts that were still pending when the + session was last suspended. When False (the default), the runtime + treats pending work as interrupted on resume. Returns: A :class:`CopilotSession` instance for the resumed session. @@ -1667,6 +1672,9 @@ async def resume_session( if enable_config_discovery is not None: payload["enableConfigDiscovery"] = enable_config_discovery + if continue_pending_work is not None: + payload["continuePendingWork"] = continue_pending_work + # TODO: disable_resume is not a keyword arg yet; keeping for future use if mcp_servers: payload["mcpServers"] = mcp_servers diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py index 397e3166a..560ea29a0 100644 --- a/python/copilot/generated/rpc.py +++ b/python/copilot/generated/rpc.py @@ -280,6 +280,60 @@ class DiscoveredMCPServerType(Enum): SSE = "sse" STDIO = "stdio" +@dataclass +class EmbeddedBlobResourceContents: + blob: str + """Base64-encoded binary content of the resource""" + + uri: str + """URI identifying the resource""" + + mime_type: str | None = None + """MIME type of the blob content""" + + @staticmethod + def from_dict(obj: Any) -> 'EmbeddedBlobResourceContents': + assert isinstance(obj, dict) + blob = from_str(obj.get("blob")) + uri = from_str(obj.get("uri")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + return EmbeddedBlobResourceContents(blob, uri, mime_type) + + def to_dict(self) -> dict: + result: dict = {} + result["blob"] = from_str(self.blob) + result["uri"] = from_str(self.uri) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + return result + +@dataclass +class EmbeddedTextResourceContents: + text: str + """Text content of the resource""" + + uri: str + """URI identifying the resource""" + + mime_type: str | None = None + """MIME type of the text content""" + + @staticmethod + def from_dict(obj: Any) -> 'EmbeddedTextResourceContents': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + uri = from_str(obj.get("uri")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + return EmbeddedTextResourceContents(text, uri, mime_type) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["uri"] = from_str(self.uri) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + return result + class ExtensionSource(Enum): """Discovery source: project (.github/extensions/) or user (~/.copilot/extensions/)""" @@ -328,6 +382,76 @@ def to_dict(self) -> dict: result["id"] = from_str(self.id) return result +class ExternalToolTextResultForLlmContentResourceLinkIconTheme(Enum): + """Theme variant this icon is intended for""" + + DARK = "dark" + LIGHT = "light" + +@dataclass +class ExternalToolTextResultForLlmContentResourceDetails: + """The embedded resource contents, either text or base64-encoded binary""" + + uri: str + """URI identifying the resource""" + + mime_type: str | None = None + """MIME type of the text content + + MIME type of the blob content + """ + text: str | None = None + """Text content of the resource""" + + blob: str | None = None + """Base64-encoded binary content of the resource""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceDetails': + assert isinstance(obj, dict) + uri = from_str(obj.get("uri")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + text = from_union([from_str, from_none], obj.get("text")) + blob = from_union([from_str, from_none], obj.get("blob")) + return ExternalToolTextResultForLlmContentResourceDetails(uri, mime_type, text, blob) + + def to_dict(self) -> dict: + result: dict = {} + result["uri"] = from_str(self.uri) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.text is not None: + result["text"] = from_union([from_str, from_none], self.text) + if self.blob is not None: + result["blob"] = from_union([from_str, from_none], self.blob) + return result + +class ExternalToolTextResultForLlmContentType(Enum): + AUDIO = "audio" + IMAGE = "image" + RESOURCE = "resource" + RESOURCE_LINK = "resource_link" + TERMINAL = "terminal" + TEXT = "text" + +class ExternalToolTextResultForLlmContentAudioType(Enum): + AUDIO = "audio" + +class ExternalToolTextResultForLlmContentImageType(Enum): + IMAGE = "image" + +class ExternalToolTextResultForLlmContentResourceType(Enum): + RESOURCE = "resource" + +class ExternalToolTextResultForLlmContentResourceLinkType(Enum): + RESOURCE_LINK = "resource_link" + +class ExternalToolTextResultForLlmContentTerminalType(Enum): + TERMINAL = "terminal" + +class ExternalToolTextResultForLlmContentTextType(Enum): + TEXT = "text" + class FilterMappingString(Enum): HIDDEN_CHARACTERS = "hidden_characters" MARKDOWN = "markdown" @@ -369,15 +493,15 @@ def to_dict(self) -> dict: return result @dataclass -class HandleToolCallResult: +class HandlePendingToolCallResult: success: bool """Whether the tool call result was handled successfully""" @staticmethod - def from_dict(obj: Any) -> 'HandleToolCallResult': + def from_dict(obj: Any) -> 'HandlePendingToolCallResult': assert isinstance(obj, dict) success = from_bool(obj.get("success")) - return HandleToolCallResult(success) + return HandlePendingToolCallResult(success) def to_dict(self) -> dict: result: dict = {} @@ -934,6 +1058,7 @@ class PermissionDecisionKind(Enum): APPROVE_FOR_LOCATION = "approve-for-location" APPROVE_FOR_SESSION = "approve-for-session" APPROVE_ONCE = "approve-once" + APPROVE_PERMANENTLY = "approve-permanently" REJECT = "reject" USER_NOT_AVAILABLE = "user-not-available" @@ -967,6 +1092,9 @@ class PermissionDecisionApproveForSessionKind(Enum): class PermissionDecisionApproveOnceKind(Enum): APPROVE_ONCE = "approve-once" +class PermissionDecisionApprovePermanentlyKind(Enum): + APPROVE_PERMANENTLY = "approve-permanently" + class PermissionDecisionRejectKind(Enum): REJECT = "reject" @@ -1982,40 +2110,6 @@ def to_dict(self) -> dict: result["parameters"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.parameters) return result -@dataclass -class ToolCallResult: - text_result_for_llm: str - """Text result to send back to the LLM""" - - error: str | None = None - """Error message if the tool call failed""" - - result_type: str | None = None - """Type of the tool result""" - - tool_telemetry: dict[str, Any] | None = None - """Telemetry data from tool execution""" - - @staticmethod - def from_dict(obj: Any) -> 'ToolCallResult': - assert isinstance(obj, dict) - text_result_for_llm = from_str(obj.get("textResultForLlm")) - error = from_union([from_str, from_none], obj.get("error")) - result_type = from_union([from_str, from_none], obj.get("resultType")) - tool_telemetry = from_union([lambda x: from_dict(lambda x: x, x), from_none], obj.get("toolTelemetry")) - return ToolCallResult(text_result_for_llm, error, result_type, tool_telemetry) - - def to_dict(self) -> dict: - result: dict = {} - result["textResultForLlm"] = from_str(self.text_result_for_llm) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result_type is not None: - result["resultType"] = from_union([from_str, from_none], self.result_type) - if self.tool_telemetry is not None: - result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) - return result - @dataclass class ToolsListRequest: model: str | None = None @@ -2176,6 +2270,22 @@ def to_dict(self) -> dict: result["count"] = from_int(self.count) return result +@dataclass +class UsageMetricsModelMetricTokenDetail: + token_count: int + """Accumulated token count for this token type""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsModelMetricTokenDetail': + assert isinstance(obj, dict) + token_count = from_int(obj.get("tokenCount")) + return UsageMetricsModelMetricTokenDetail(token_count) + + def to_dict(self) -> dict: + result: dict = {} + result["tokenCount"] = from_int(self.token_count) + return result + @dataclass class UsageMetricsModelMetricUsage: """Token usage metrics for this model""" @@ -2215,6 +2325,22 @@ def to_dict(self) -> dict: result["reasoningTokens"] = from_union([from_int, from_none], self.reasoning_tokens) return result +@dataclass +class UsageMetricsTokenDetail: + token_count: int + """Accumulated token count for this token type""" + + @staticmethod + def from_dict(obj: Any) -> 'UsageMetricsTokenDetail': + assert isinstance(obj, dict) + token_count = from_int(obj.get("tokenCount")) + return UsageMetricsTokenDetail(token_count) + + def to_dict(self) -> dict: + result: dict = {} + result["tokenCount"] = from_int(self.token_count) + return result + @dataclass class WorkspacesCreateFileRequest: content: str @@ -2493,6 +2619,179 @@ def to_dict(self) -> dict: result["pid"] = from_union([from_int, from_none], self.pid) return result +@dataclass +class ExternalToolTextResultForLlmContentResourceLinkIcon: + """Icon image for a resource""" + + src: str + """URL or path to the icon image""" + + mime_type: str | None = None + """MIME type of the icon image""" + + sizes: list[str] | None = None + """Available icon sizes (e.g., ['16x16', '32x32'])""" + + theme: ExternalToolTextResultForLlmContentResourceLinkIconTheme | None = None + """Theme variant this icon is intended for""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceLinkIcon': + assert isinstance(obj, dict) + src = from_str(obj.get("src")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + sizes = from_union([lambda x: from_list(from_str, x), from_none], obj.get("sizes")) + theme = from_union([ExternalToolTextResultForLlmContentResourceLinkIconTheme, from_none], obj.get("theme")) + return ExternalToolTextResultForLlmContentResourceLinkIcon(src, mime_type, sizes, theme) + + def to_dict(self) -> dict: + result: dict = {} + result["src"] = from_str(self.src) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.sizes is not None: + result["sizes"] = from_union([lambda x: from_list(from_str, x), from_none], self.sizes) + if self.theme is not None: + result["theme"] = from_union([lambda x: to_enum(ExternalToolTextResultForLlmContentResourceLinkIconTheme, x), from_none], self.theme) + return result + +@dataclass +class ExternalToolTextResultForLlmContentAudio: + """Audio content block with base64-encoded data""" + + data: str + """Base64-encoded audio data""" + + mime_type: str + """MIME type of the audio (e.g., audio/wav, audio/mpeg)""" + + type: ExternalToolTextResultForLlmContentAudioType + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentAudio': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + mime_type = from_str(obj.get("mimeType")) + type = ExternalToolTextResultForLlmContentAudioType(obj.get("type")) + return ExternalToolTextResultForLlmContentAudio(data, mime_type, type) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["mimeType"] = from_str(self.mime_type) + result["type"] = to_enum(ExternalToolTextResultForLlmContentAudioType, self.type) + return result + +@dataclass +class ExternalToolTextResultForLlmContentImage: + """Image content block with base64-encoded data""" + + data: str + """Base64-encoded image data""" + + mime_type: str + """MIME type of the image (e.g., image/png, image/jpeg)""" + + type: ExternalToolTextResultForLlmContentImageType + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentImage': + assert isinstance(obj, dict) + data = from_str(obj.get("data")) + mime_type = from_str(obj.get("mimeType")) + type = ExternalToolTextResultForLlmContentImageType(obj.get("type")) + return ExternalToolTextResultForLlmContentImage(data, mime_type, type) + + def to_dict(self) -> dict: + result: dict = {} + result["data"] = from_str(self.data) + result["mimeType"] = from_str(self.mime_type) + result["type"] = to_enum(ExternalToolTextResultForLlmContentImageType, self.type) + return result + +@dataclass +class ExternalToolTextResultForLlmContentResource: + """Embedded resource content block with inline text or binary data""" + + resource: ExternalToolTextResultForLlmContentResourceDetails + """The embedded resource contents, either text or base64-encoded binary""" + + type: ExternalToolTextResultForLlmContentResourceType + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResource': + assert isinstance(obj, dict) + resource = ExternalToolTextResultForLlmContentResourceDetails.from_dict(obj.get("resource")) + type = ExternalToolTextResultForLlmContentResourceType(obj.get("type")) + return ExternalToolTextResultForLlmContentResource(resource, type) + + def to_dict(self) -> dict: + result: dict = {} + result["resource"] = to_class(ExternalToolTextResultForLlmContentResourceDetails, self.resource) + result["type"] = to_enum(ExternalToolTextResultForLlmContentResourceType, self.type) + return result + +@dataclass +class ExternalToolTextResultForLlmContentTerminal: + """Terminal/shell output content block with optional exit code and working directory""" + + text: str + """Terminal/shell output text""" + + type: ExternalToolTextResultForLlmContentTerminalType + """Content block type discriminator""" + + cwd: str | None = None + """Working directory where the command was executed""" + + exit_code: float | None = None + """Process exit code, if the command has completed""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentTerminal': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + type = ExternalToolTextResultForLlmContentTerminalType(obj.get("type")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + exit_code = from_union([from_float, from_none], obj.get("exitCode")) + return ExternalToolTextResultForLlmContentTerminal(text, type, cwd, exit_code) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["type"] = to_enum(ExternalToolTextResultForLlmContentTerminalType, self.type) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.exit_code is not None: + result["exitCode"] = from_union([to_float, from_none], self.exit_code) + return result + +@dataclass +class ExternalToolTextResultForLlmContentText: + """Plain text content block""" + + text: str + """The text content""" + + type: ExternalToolTextResultForLlmContentTextType + """Content block type discriminator""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentText': + assert isinstance(obj, dict) + text = from_str(obj.get("text")) + type = ExternalToolTextResultForLlmContentTextType(obj.get("type")) + return ExternalToolTextResultForLlmContentText(text, type) + + def to_dict(self) -> dict: + result: dict = {} + result["text"] = from_str(self.text) + result["type"] = to_enum(ExternalToolTextResultForLlmContentTextType, self.type) + return result + # Experimental: this type is part of an experimental API and may change or be removed. @dataclass class HistoryCompactResult: @@ -3251,6 +3550,27 @@ def to_dict(self) -> dict: result["kind"] = to_enum(PermissionDecisionApproveOnceKind, self.kind) return result +@dataclass +class PermissionDecisionApprovePermanently: + domain: str + """The URL domain to approve permanently""" + + kind: PermissionDecisionApprovePermanentlyKind + """Approved and persisted across sessions""" + + @staticmethod + def from_dict(obj: Any) -> 'PermissionDecisionApprovePermanently': + assert isinstance(obj, dict) + domain = from_str(obj.get("domain")) + kind = PermissionDecisionApprovePermanentlyKind(obj.get("kind")) + return PermissionDecisionApprovePermanently(domain, kind) + + def to_dict(self) -> dict: + result: dict = {} + result["domain"] = from_str(self.domain) + result["kind"] = to_enum(PermissionDecisionApprovePermanentlyKind, self.kind) + return result + @dataclass class PermissionDecisionReject: kind: PermissionDecisionRejectKind @@ -3525,34 +3845,6 @@ def to_dict(self) -> dict: result["tools"] = from_list(lambda x: to_class(Tool, x), self.tools) return result -@dataclass -class ToolsHandlePendingToolCallRequest: - request_id: str - """Request ID of the pending tool call""" - - error: str | None = None - """Error message if the tool call failed""" - - result: ToolCallResult | str | None = None - """Tool call result (string or expanded result object)""" - - @staticmethod - def from_dict(obj: Any) -> 'ToolsHandlePendingToolCallRequest': - assert isinstance(obj, dict) - request_id = from_str(obj.get("requestId")) - error = from_union([from_str, from_none], obj.get("error")) - result = from_union([ToolCallResult.from_dict, from_str, from_none], obj.get("result")) - return ToolsHandlePendingToolCallRequest(request_id, error, result) - - def to_dict(self) -> dict: - result: dict = {} - result["requestId"] = from_str(self.request_id) - if self.error is not None: - result["error"] = from_union([from_str, from_none], self.error) - if self.result is not None: - result["result"] = from_union([lambda x: to_class(ToolCallResult, x), from_str, from_none], self.result) - return result - @dataclass class UIElicitationArrayAnyOfFieldItems: any_of: list[UIElicitationArrayAnyOfFieldItemsAnyOf] @@ -3807,17 +4099,29 @@ class UsageMetricsModelMetric: usage: UsageMetricsModelMetricUsage """Token usage metrics for this model""" + token_details: dict[str, UsageMetricsModelMetricTokenDetail] | None = None + """Token count details per type""" + + total_nano_aiu: int | None = None + """Accumulated nano-AI units cost for this model""" + @staticmethod def from_dict(obj: Any) -> 'UsageMetricsModelMetric': assert isinstance(obj, dict) requests = UsageMetricsModelMetricRequests.from_dict(obj.get("requests")) usage = UsageMetricsModelMetricUsage.from_dict(obj.get("usage")) - return UsageMetricsModelMetric(requests, usage) + token_details = from_union([lambda x: from_dict(UsageMetricsModelMetricTokenDetail.from_dict, x), from_none], obj.get("tokenDetails")) + total_nano_aiu = from_union([from_int, from_none], obj.get("totalNanoAiu")) + return UsageMetricsModelMetric(requests, usage, token_details, total_nano_aiu) def to_dict(self) -> dict: result: dict = {} result["requests"] = to_class(UsageMetricsModelMetricRequests, self.requests) result["usage"] = to_class(UsageMetricsModelMetricUsage, self.usage) + if self.token_details is not None: + result["tokenDetails"] = from_union([lambda x: from_dict(lambda x: to_class(UsageMetricsModelMetricTokenDetail, x), x), from_none], self.token_details) + if self.total_nano_aiu is not None: + result["totalNanoAiu"] = from_union([from_int, from_none], self.total_nano_aiu) return result @dataclass @@ -3936,6 +4240,175 @@ def to_dict(self) -> dict: result["extensions"] = from_list(lambda x: to_class(Extension, x), self.extensions) return result +@dataclass +class ExternalToolTextResultForLlmContent: + """A content block within a tool result, which may be text, terminal output, image, audio, + or a resource + + Plain text content block + + Terminal/shell output content block with optional exit code and working directory + + Image content block with base64-encoded data + + Audio content block with base64-encoded data + + Resource link content block referencing an external resource + + Embedded resource content block with inline text or binary data + """ + type: ExternalToolTextResultForLlmContentType + """Content block type discriminator""" + + text: str | None = None + """The text content + + Terminal/shell output text + """ + cwd: str | None = None + """Working directory where the command was executed""" + + exit_code: float | None = None + """Process exit code, if the command has completed""" + + data: str | None = None + """Base64-encoded image data + + Base64-encoded audio data + """ + mime_type: str | None = None + """MIME type of the image (e.g., image/png, image/jpeg) + + MIME type of the audio (e.g., audio/wav, audio/mpeg) + + MIME type of the resource content + """ + description: str | None = None + """Human-readable description of the resource""" + + icons: list[ExternalToolTextResultForLlmContentResourceLinkIcon] | None = None + """Icons associated with this resource""" + + name: str | None = None + """Resource name identifier""" + + size: float | None = None + """Size of the resource in bytes""" + + title: str | None = None + """Human-readable display title for the resource""" + + uri: str | None = None + """URI identifying the resource""" + + resource: ExternalToolTextResultForLlmContentResourceDetails | None = None + """The embedded resource contents, either text or base64-encoded binary""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContent': + assert isinstance(obj, dict) + type = ExternalToolTextResultForLlmContentType(obj.get("type")) + text = from_union([from_str, from_none], obj.get("text")) + cwd = from_union([from_str, from_none], obj.get("cwd")) + exit_code = from_union([from_float, from_none], obj.get("exitCode")) + data = from_union([from_str, from_none], obj.get("data")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + description = from_union([from_str, from_none], obj.get("description")) + icons = from_union([lambda x: from_list(ExternalToolTextResultForLlmContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons")) + name = from_union([from_str, from_none], obj.get("name")) + size = from_union([from_float, from_none], obj.get("size")) + title = from_union([from_str, from_none], obj.get("title")) + uri = from_union([from_str, from_none], obj.get("uri")) + resource = from_union([ExternalToolTextResultForLlmContentResourceDetails.from_dict, from_none], obj.get("resource")) + return ExternalToolTextResultForLlmContent(type, text, cwd, exit_code, data, mime_type, description, icons, name, size, title, uri, resource) + + def to_dict(self) -> dict: + result: dict = {} + result["type"] = to_enum(ExternalToolTextResultForLlmContentType, self.type) + if self.text is not None: + result["text"] = from_union([from_str, from_none], self.text) + if self.cwd is not None: + result["cwd"] = from_union([from_str, from_none], self.cwd) + if self.exit_code is not None: + result["exitCode"] = from_union([to_float, from_none], self.exit_code) + if self.data is not None: + result["data"] = from_union([from_str, from_none], self.data) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.icons is not None: + result["icons"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmContentResourceLinkIcon, x), x), from_none], self.icons) + if self.name is not None: + result["name"] = from_union([from_str, from_none], self.name) + if self.size is not None: + result["size"] = from_union([to_float, from_none], self.size) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + if self.uri is not None: + result["uri"] = from_union([from_str, from_none], self.uri) + if self.resource is not None: + result["resource"] = from_union([lambda x: to_class(ExternalToolTextResultForLlmContentResourceDetails, x), from_none], self.resource) + return result + +@dataclass +class ExternalToolTextResultForLlmContentResourceLink: + """Resource link content block referencing an external resource""" + + name: str + """Resource name identifier""" + + type: ExternalToolTextResultForLlmContentResourceLinkType + """Content block type discriminator""" + + uri: str + """URI identifying the resource""" + + description: str | None = None + """Human-readable description of the resource""" + + icons: list[ExternalToolTextResultForLlmContentResourceLinkIcon] | None = None + """Icons associated with this resource""" + + mime_type: str | None = None + """MIME type of the resource content""" + + size: float | None = None + """Size of the resource in bytes""" + + title: str | None = None + """Human-readable display title for the resource""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlmContentResourceLink': + assert isinstance(obj, dict) + name = from_str(obj.get("name")) + type = ExternalToolTextResultForLlmContentResourceLinkType(obj.get("type")) + uri = from_str(obj.get("uri")) + description = from_union([from_str, from_none], obj.get("description")) + icons = from_union([lambda x: from_list(ExternalToolTextResultForLlmContentResourceLinkIcon.from_dict, x), from_none], obj.get("icons")) + mime_type = from_union([from_str, from_none], obj.get("mimeType")) + size = from_union([from_float, from_none], obj.get("size")) + title = from_union([from_str, from_none], obj.get("title")) + return ExternalToolTextResultForLlmContentResourceLink(name, type, uri, description, icons, mime_type, size, title) + + def to_dict(self) -> dict: + result: dict = {} + result["name"] = from_str(self.name) + result["type"] = to_enum(ExternalToolTextResultForLlmContentResourceLinkType, self.type) + result["uri"] = from_str(self.uri) + if self.description is not None: + result["description"] = from_union([from_str, from_none], self.description) + if self.icons is not None: + result["icons"] = from_union([lambda x: from_list(lambda x: to_class(ExternalToolTextResultForLlmContentResourceLinkIcon, x), x), from_none], self.icons) + if self.mime_type is not None: + result["mimeType"] = from_union([from_str, from_none], self.mime_type) + if self.size is not None: + result["size"] = from_union([to_float, from_none], self.size) + if self.title is not None: + result["title"] = from_union([from_str, from_none], self.title) + return result + @dataclass class InstructionsGetSourcesResult: sources: list[InstructionsSources] @@ -4060,6 +4533,8 @@ class PermissionDecision: Approved and persisted for this project location + Approved and persisted across sessions + Denied by the user during an interactive prompt Denied because user confirmation was unavailable @@ -4069,6 +4544,11 @@ class PermissionDecision: The approval to persist for this location """ + domain: str | None = None + """The URL domain to approve for this session + + The URL domain to approve permanently + """ location_key: str | None = None """The location key (git root or cwd) to persist the approval to""" @@ -4080,15 +4560,18 @@ def from_dict(obj: Any) -> 'PermissionDecision': assert isinstance(obj, dict) kind = PermissionDecisionKind(obj.get("kind")) approval = from_union([PermissionDecisionApproveForIonApproval.from_dict, from_none], obj.get("approval")) + domain = from_union([from_str, from_none], obj.get("domain")) location_key = from_union([from_str, from_none], obj.get("locationKey")) feedback = from_union([from_str, from_none], obj.get("feedback")) - return PermissionDecision(kind, approval, location_key, feedback) + return PermissionDecision(kind, approval, domain, location_key, feedback) def to_dict(self) -> dict: result: dict = {} result["kind"] = to_enum(PermissionDecisionKind, self.kind) if self.approval is not None: result["approval"] = from_union([lambda x: to_class(PermissionDecisionApproveForIonApproval, x), from_none], self.approval) + if self.domain is not None: + result["domain"] = from_union([from_str, from_none], self.domain) if self.location_key is not None: result["locationKey"] = from_union([from_str, from_none], self.location_key) if self.feedback is not None: @@ -4123,23 +4606,30 @@ def to_dict(self) -> dict: @dataclass class PermissionDecisionApproveForSession: - approval: PermissionDecisionApproveForSessionApproval - """The approval to add as a session-scoped rule""" - kind: PermissionDecisionApproveForSessionKind """Approved and remembered for the rest of the session""" + approval: PermissionDecisionApproveForSessionApproval | None = None + """The approval to add as a session-scoped rule""" + + domain: str | None = None + """The URL domain to approve for this session""" + @staticmethod def from_dict(obj: Any) -> 'PermissionDecisionApproveForSession': assert isinstance(obj, dict) - approval = PermissionDecisionApproveForSessionApproval.from_dict(obj.get("approval")) kind = PermissionDecisionApproveForSessionKind(obj.get("kind")) - return PermissionDecisionApproveForSession(approval, kind) + approval = from_union([PermissionDecisionApproveForSessionApproval.from_dict, from_none], obj.get("approval")) + domain = from_union([from_str, from_none], obj.get("domain")) + return PermissionDecisionApproveForSession(kind, approval, domain) def to_dict(self) -> dict: result: dict = {} - result["approval"] = to_class(PermissionDecisionApproveForSessionApproval, self.approval) result["kind"] = to_enum(PermissionDecisionApproveForSessionKind, self.kind) + if self.approval is not None: + result["approval"] = from_union([lambda x: to_class(PermissionDecisionApproveForSessionApproval, x), from_none], self.approval) + if self.domain is not None: + result["domain"] = from_union([from_str, from_none], self.domain) return result @dataclass @@ -4449,6 +4939,12 @@ class UsageGetMetricsResult: current_model: str | None = None """Currently active model identifier""" + token_details: dict[str, UsageMetricsTokenDetail] | None = None + """Session-wide per-token-type accumulated token counts""" + + total_nano_aiu: int | None = None + """Session-wide accumulated nano-AI units cost""" + @staticmethod def from_dict(obj: Any) -> 'UsageGetMetricsResult': assert isinstance(obj, dict) @@ -4461,7 +4957,9 @@ def from_dict(obj: Any) -> 'UsageGetMetricsResult': total_premium_request_cost = from_float(obj.get("totalPremiumRequestCost")) total_user_requests = from_int(obj.get("totalUserRequests")) current_model = from_union([from_str, from_none], obj.get("currentModel")) - return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model) + token_details = from_union([lambda x: from_dict(UsageMetricsTokenDetail.from_dict, x), from_none], obj.get("tokenDetails")) + total_nano_aiu = from_union([from_int, from_none], obj.get("totalNanoAiu")) + return UsageGetMetricsResult(code_changes, last_call_input_tokens, last_call_output_tokens, model_metrics, session_start_time, total_api_duration_ms, total_premium_request_cost, total_user_requests, current_model, token_details, total_nano_aiu) def to_dict(self) -> dict: result: dict = {} @@ -4475,6 +4973,10 @@ def to_dict(self) -> dict: result["totalUserRequests"] = from_int(self.total_user_requests) if self.current_model is not None: result["currentModel"] = from_union([from_str, from_none], self.current_model) + if self.token_details is not None: + result["tokenDetails"] = from_union([lambda x: from_dict(lambda x: to_class(UsageMetricsTokenDetail, x), x), from_none], self.token_details) + if self.total_nano_aiu is not None: + result["totalNanoAiu"] = from_union([from_int, from_none], self.total_nano_aiu) return result @dataclass @@ -4493,6 +4995,55 @@ def to_dict(self) -> dict: result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace) return result +@dataclass +class ExternalToolTextResultForLlm: + """Expanded external tool result payload""" + + text_result_for_llm: str + """Text result returned to the model""" + + contents: list[ExternalToolTextResultForLlmContent] | None = None + """Structured content blocks from the tool""" + + error: str | None = None + """Optional error message for failed executions""" + + result_type: str | None = None + """Execution outcome classification. Optional for back-compat; normalized to 'success' (or + 'failure' when error is present) when missing or unrecognized. + """ + session_log: str | None = None + """Detailed log content for timeline display""" + + tool_telemetry: dict[str, Any] | None = None + """Optional tool-specific telemetry""" + + @staticmethod + def from_dict(obj: Any) -> 'ExternalToolTextResultForLlm': + assert isinstance(obj, dict) + text_result_for_llm = from_str(obj.get("textResultForLlm")) + 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) + + def to_dict(self) -> dict: + result: dict = {} + result["textResultForLlm"] = from_str(self.text_result_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: + result["error"] = from_union([from_str, from_none], self.error) + if self.result_type is not None: + result["resultType"] = from_union([from_str, from_none], self.result_type) + if self.session_log is not None: + result["sessionLog"] = from_union([from_str, from_none], self.session_log) + if self.tool_telemetry is not None: + result["toolTelemetry"] = from_union([lambda x: from_dict(lambda x: x, x), from_none], self.tool_telemetry) + return result + @dataclass class PermissionDecisionRequest: request_id: str @@ -4542,6 +5093,34 @@ def to_dict(self) -> dict: result["required"] = from_union([lambda x: from_list(from_str, x), from_none], self.required) return result +@dataclass +class HandlePendingToolCallRequest: + request_id: str + """Request ID of the pending tool call""" + + error: str | None = None + """Error message if the tool call failed""" + + result: ExternalToolTextResultForLlm | str | None = None + """Tool call result (string or expanded result object)""" + + @staticmethod + def from_dict(obj: Any) -> 'HandlePendingToolCallRequest': + assert isinstance(obj, dict) + request_id = from_str(obj.get("requestId")) + error = from_union([from_str, from_none], obj.get("error")) + result = from_union([ExternalToolTextResultForLlm.from_dict, from_str, from_none], obj.get("result")) + return HandlePendingToolCallRequest(request_id, error, result) + + def to_dict(self) -> dict: + result: dict = {} + result["requestId"] = from_str(self.request_id) + if self.error is not None: + result["error"] = from_union([from_str, from_none], self.error) + if self.result is not None: + result["result"] = from_union([lambda x: to_class(ExternalToolTextResultForLlm, x), from_str, from_none], self.result) + return result + @dataclass class UIElicitationRequest: message: str @@ -4975,18 +5554,33 @@ class RPC: discovered_mcp_server: DiscoveredMCPServer discovered_mcp_server_source: MCPServerSource discovered_mcp_server_type: DiscoveredMCPServerType + embedded_blob_resource_contents: EmbeddedBlobResourceContents + embedded_text_resource_contents: EmbeddedTextResourceContents extension: Extension extension_list: ExtensionList extensions_disable_request: ExtensionsDisableRequest extensions_enable_request: ExtensionsEnableRequest extension_source: ExtensionSource extension_status: ExtensionStatus + external_tool_result: ExternalToolTextResultForLlm | str + external_tool_text_result_for_llm: ExternalToolTextResultForLlm + 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 + external_tool_text_result_for_llm_content_resource: ExternalToolTextResultForLlmContentResource + external_tool_text_result_for_llm_content_resource_details: ExternalToolTextResultForLlmContentResourceDetails + external_tool_text_result_for_llm_content_resource_link: ExternalToolTextResultForLlmContentResourceLink + external_tool_text_result_for_llm_content_resource_link_icon: ExternalToolTextResultForLlmContentResourceLinkIcon + 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 fleet_start_request: FleetStartRequest fleet_start_result: FleetStartResult - handle_tool_call_result: HandleToolCallResult + handle_pending_tool_call_request: HandlePendingToolCallRequest + handle_pending_tool_call_result: HandlePendingToolCallResult history_compact_context_window: HistoryCompactContextWindow history_compact_result: HistoryCompactResult history_truncate_request: HistoryTruncateRequest @@ -5056,6 +5650,7 @@ class RPC: permission_decision_approve_for_session_approval_read: PermissionDecisionApproveForSessionApprovalRead permission_decision_approve_for_session_approval_write: PermissionDecisionApproveForSessionApprovalWrite permission_decision_approve_once: PermissionDecisionApproveOnce + permission_decision_approve_permanently: PermissionDecisionApprovePermanently permission_decision_reject: PermissionDecisionReject permission_decision_request: PermissionDecisionRequest permission_decision_user_not_available: PermissionDecisionUserNotAvailable @@ -5128,10 +5723,7 @@ class RPC: tasks_start_agent_request: TasksStartAgentRequest tasks_start_agent_result: TasksStartAgentResult tool: Tool - tool_call_result: ToolCallResult tool_list: ToolList - tools_handle_pending_tool_call: ToolCallResult | str - tools_handle_pending_tool_call_request: ToolsHandlePendingToolCallRequest tools_list_request: ToolsListRequest ui_elicitation_array_any_of_field: UIElicitationArrayAnyOfField ui_elicitation_array_any_of_field_items: UIElicitationArrayAnyOfFieldItems @@ -5159,7 +5751,9 @@ class RPC: usage_metrics_code_changes: UsageMetricsCodeChanges usage_metrics_model_metric: UsageMetricsModelMetric usage_metrics_model_metric_requests: UsageMetricsModelMetricRequests + usage_metrics_model_metric_token_detail: UsageMetricsModelMetricTokenDetail usage_metrics_model_metric_usage: UsageMetricsModelMetricUsage + usage_metrics_token_detail: UsageMetricsTokenDetail workspaces_create_file_request: WorkspacesCreateFileRequest workspaces_get_workspace_result: WorkspacesGetWorkspaceResult workspaces_list_files_result: WorkspacesListFilesResult @@ -5185,18 +5779,33 @@ def from_dict(obj: Any) -> 'RPC': 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")) + embedded_blob_resource_contents = EmbeddedBlobResourceContents.from_dict(obj.get("EmbeddedBlobResourceContents")) + embedded_text_resource_contents = EmbeddedTextResourceContents.from_dict(obj.get("EmbeddedTextResourceContents")) extension = Extension.from_dict(obj.get("Extension")) extension_list = ExtensionList.from_dict(obj.get("ExtensionList")) extensions_disable_request = ExtensionsDisableRequest.from_dict(obj.get("ExtensionsDisableRequest")) extensions_enable_request = ExtensionsEnableRequest.from_dict(obj.get("ExtensionsEnableRequest")) extension_source = ExtensionSource(obj.get("ExtensionSource")) 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_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")) + external_tool_text_result_for_llm_content_resource = ExternalToolTextResultForLlmContentResource.from_dict(obj.get("ExternalToolTextResultForLlmContentResource")) + external_tool_text_result_for_llm_content_resource_details = ExternalToolTextResultForLlmContentResourceDetails.from_dict(obj.get("ExternalToolTextResultForLlmContentResourceDetails")) + external_tool_text_result_for_llm_content_resource_link = ExternalToolTextResultForLlmContentResourceLink.from_dict(obj.get("ExternalToolTextResultForLlmContentResourceLink")) + external_tool_text_result_for_llm_content_resource_link_icon = ExternalToolTextResultForLlmContentResourceLinkIcon.from_dict(obj.get("ExternalToolTextResultForLlmContentResourceLinkIcon")) + 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")) fleet_start_request = FleetStartRequest.from_dict(obj.get("FleetStartRequest")) fleet_start_result = FleetStartResult.from_dict(obj.get("FleetStartResult")) - handle_tool_call_result = HandleToolCallResult.from_dict(obj.get("HandleToolCallResult")) + handle_pending_tool_call_request = HandlePendingToolCallRequest.from_dict(obj.get("HandlePendingToolCallRequest")) + handle_pending_tool_call_result = HandlePendingToolCallResult.from_dict(obj.get("HandlePendingToolCallResult")) history_compact_context_window = HistoryCompactContextWindow.from_dict(obj.get("HistoryCompactContextWindow")) history_compact_result = HistoryCompactResult.from_dict(obj.get("HistoryCompactResult")) history_truncate_request = HistoryTruncateRequest.from_dict(obj.get("HistoryTruncateRequest")) @@ -5266,6 +5875,7 @@ def from_dict(obj: Any) -> 'RPC': permission_decision_approve_for_session_approval_read = PermissionDecisionApproveForSessionApprovalRead.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalRead")) permission_decision_approve_for_session_approval_write = PermissionDecisionApproveForSessionApprovalWrite.from_dict(obj.get("PermissionDecisionApproveForSessionApprovalWrite")) permission_decision_approve_once = PermissionDecisionApproveOnce.from_dict(obj.get("PermissionDecisionApproveOnce")) + permission_decision_approve_permanently = PermissionDecisionApprovePermanently.from_dict(obj.get("PermissionDecisionApprovePermanently")) permission_decision_reject = PermissionDecisionReject.from_dict(obj.get("PermissionDecisionReject")) permission_decision_request = PermissionDecisionRequest.from_dict(obj.get("PermissionDecisionRequest")) permission_decision_user_not_available = PermissionDecisionUserNotAvailable.from_dict(obj.get("PermissionDecisionUserNotAvailable")) @@ -5338,10 +5948,7 @@ def from_dict(obj: Any) -> 'RPC': tasks_start_agent_request = TasksStartAgentRequest.from_dict(obj.get("TasksStartAgentRequest")) tasks_start_agent_result = TasksStartAgentResult.from_dict(obj.get("TasksStartAgentResult")) tool = Tool.from_dict(obj.get("Tool")) - tool_call_result = ToolCallResult.from_dict(obj.get("ToolCallResult")) tool_list = ToolList.from_dict(obj.get("ToolList")) - tools_handle_pending_tool_call = from_union([ToolCallResult.from_dict, from_str], obj.get("ToolsHandlePendingToolCall")) - tools_handle_pending_tool_call_request = ToolsHandlePendingToolCallRequest.from_dict(obj.get("ToolsHandlePendingToolCallRequest")) tools_list_request = ToolsListRequest.from_dict(obj.get("ToolsListRequest")) ui_elicitation_array_any_of_field = UIElicitationArrayAnyOfField.from_dict(obj.get("UIElicitationArrayAnyOfField")) ui_elicitation_array_any_of_field_items = UIElicitationArrayAnyOfFieldItems.from_dict(obj.get("UIElicitationArrayAnyOfFieldItems")) @@ -5369,13 +5976,15 @@ def from_dict(obj: Any) -> 'RPC': usage_metrics_code_changes = UsageMetricsCodeChanges.from_dict(obj.get("UsageMetricsCodeChanges")) usage_metrics_model_metric = UsageMetricsModelMetric.from_dict(obj.get("UsageMetricsModelMetric")) usage_metrics_model_metric_requests = UsageMetricsModelMetricRequests.from_dict(obj.get("UsageMetricsModelMetricRequests")) + usage_metrics_model_metric_token_detail = UsageMetricsModelMetricTokenDetail.from_dict(obj.get("UsageMetricsModelMetricTokenDetail")) usage_metrics_model_metric_usage = UsageMetricsModelMetricUsage.from_dict(obj.get("UsageMetricsModelMetricUsage")) + usage_metrics_token_detail = UsageMetricsTokenDetail.from_dict(obj.get("UsageMetricsTokenDetail")) workspaces_create_file_request = WorkspacesCreateFileRequest.from_dict(obj.get("WorkspacesCreateFileRequest")) workspaces_get_workspace_result = WorkspacesGetWorkspaceResult.from_dict(obj.get("WorkspacesGetWorkspaceResult")) workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult")) workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest")) workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult")) - return RPC(account_get_quota_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, commands_handle_pending_command_request, commands_handle_pending_command_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, filter_mapping, filter_mapping_string, filter_mapping_value, fleet_start_request, fleet_start_result, handle_tool_call_result, history_compact_context_window, history_compact_result, history_truncate_request, history_truncate_result, instructions_get_sources_result, instructions_sources, 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_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, 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_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_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_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, 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, 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_start_agent_request, tasks_start_agent_result, tool, tool_call_result, tool_list, tools_handle_pending_tool_call, tools_handle_pending_tool_call_request, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, 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_usage, 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, commands_handle_pending_command_request, commands_handle_pending_command_result, current_model, discovered_mcp_server, discovered_mcp_server_source, discovered_mcp_server_type, embedded_blob_resource_contents, embedded_text_resource_contents, 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_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_policy, 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_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_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, 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, 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_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) def to_dict(self) -> dict: result: dict = {} @@ -5395,18 +6004,33 @@ def to_dict(self) -> dict: 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["EmbeddedBlobResourceContents"] = to_class(EmbeddedBlobResourceContents, self.embedded_blob_resource_contents) + result["EmbeddedTextResourceContents"] = to_class(EmbeddedTextResourceContents, self.embedded_text_resource_contents) result["Extension"] = to_class(Extension, self.extension) result["ExtensionList"] = to_class(ExtensionList, self.extension_list) result["ExtensionsDisableRequest"] = to_class(ExtensionsDisableRequest, self.extensions_disable_request) result["ExtensionsEnableRequest"] = to_class(ExtensionsEnableRequest, self.extensions_enable_request) result["ExtensionSource"] = to_enum(ExtensionSource, self.extension_source) 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["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) + result["ExternalToolTextResultForLlmContentResource"] = to_class(ExternalToolTextResultForLlmContentResource, self.external_tool_text_result_for_llm_content_resource) + result["ExternalToolTextResultForLlmContentResourceDetails"] = to_class(ExternalToolTextResultForLlmContentResourceDetails, self.external_tool_text_result_for_llm_content_resource_details) + result["ExternalToolTextResultForLlmContentResourceLink"] = to_class(ExternalToolTextResultForLlmContentResourceLink, self.external_tool_text_result_for_llm_content_resource_link) + result["ExternalToolTextResultForLlmContentResourceLinkIcon"] = to_class(ExternalToolTextResultForLlmContentResourceLinkIcon, self.external_tool_text_result_for_llm_content_resource_link_icon) + 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["FleetStartRequest"] = to_class(FleetStartRequest, self.fleet_start_request) result["FleetStartResult"] = to_class(FleetStartResult, self.fleet_start_result) - result["HandleToolCallResult"] = to_class(HandleToolCallResult, self.handle_tool_call_result) + result["HandlePendingToolCallRequest"] = to_class(HandlePendingToolCallRequest, self.handle_pending_tool_call_request) + result["HandlePendingToolCallResult"] = to_class(HandlePendingToolCallResult, self.handle_pending_tool_call_result) result["HistoryCompactContextWindow"] = to_class(HistoryCompactContextWindow, self.history_compact_context_window) result["HistoryCompactResult"] = to_class(HistoryCompactResult, self.history_compact_result) result["HistoryTruncateRequest"] = to_class(HistoryTruncateRequest, self.history_truncate_request) @@ -5476,6 +6100,7 @@ def to_dict(self) -> dict: result["PermissionDecisionApproveForSessionApprovalRead"] = to_class(PermissionDecisionApproveForSessionApprovalRead, self.permission_decision_approve_for_session_approval_read) result["PermissionDecisionApproveForSessionApprovalWrite"] = to_class(PermissionDecisionApproveForSessionApprovalWrite, self.permission_decision_approve_for_session_approval_write) result["PermissionDecisionApproveOnce"] = to_class(PermissionDecisionApproveOnce, self.permission_decision_approve_once) + result["PermissionDecisionApprovePermanently"] = to_class(PermissionDecisionApprovePermanently, self.permission_decision_approve_permanently) result["PermissionDecisionReject"] = to_class(PermissionDecisionReject, self.permission_decision_reject) result["PermissionDecisionRequest"] = to_class(PermissionDecisionRequest, self.permission_decision_request) result["PermissionDecisionUserNotAvailable"] = to_class(PermissionDecisionUserNotAvailable, self.permission_decision_user_not_available) @@ -5548,10 +6173,7 @@ def to_dict(self) -> dict: result["TasksStartAgentRequest"] = to_class(TasksStartAgentRequest, self.tasks_start_agent_request) result["TasksStartAgentResult"] = to_class(TasksStartAgentResult, self.tasks_start_agent_result) result["Tool"] = to_class(Tool, self.tool) - result["ToolCallResult"] = to_class(ToolCallResult, self.tool_call_result) result["ToolList"] = to_class(ToolList, self.tool_list) - result["ToolsHandlePendingToolCall"] = from_union([lambda x: to_class(ToolCallResult, x), from_str], self.tools_handle_pending_tool_call) - result["ToolsHandlePendingToolCallRequest"] = to_class(ToolsHandlePendingToolCallRequest, self.tools_handle_pending_tool_call_request) result["ToolsListRequest"] = to_class(ToolsListRequest, self.tools_list_request) result["UIElicitationArrayAnyOfField"] = to_class(UIElicitationArrayAnyOfField, self.ui_elicitation_array_any_of_field) result["UIElicitationArrayAnyOfFieldItems"] = to_class(UIElicitationArrayAnyOfFieldItems, self.ui_elicitation_array_any_of_field_items) @@ -5579,7 +6201,9 @@ def to_dict(self) -> dict: result["UsageMetricsCodeChanges"] = to_class(UsageMetricsCodeChanges, self.usage_metrics_code_changes) result["UsageMetricsModelMetric"] = to_class(UsageMetricsModelMetric, self.usage_metrics_model_metric) result["UsageMetricsModelMetricRequests"] = to_class(UsageMetricsModelMetricRequests, self.usage_metrics_model_metric_requests) + result["UsageMetricsModelMetricTokenDetail"] = to_class(UsageMetricsModelMetricTokenDetail, self.usage_metrics_model_metric_token_detail) result["UsageMetricsModelMetricUsage"] = to_class(UsageMetricsModelMetricUsage, self.usage_metrics_model_metric_usage) + result["UsageMetricsTokenDetail"] = to_class(UsageMetricsTokenDetail, self.usage_metrics_token_detail) result["WorkspacesCreateFileRequest"] = to_class(WorkspacesCreateFileRequest, self.workspaces_create_file_request) result["WorkspacesGetWorkspaceResult"] = to_class(WorkspacesGetWorkspaceResult, self.workspaces_get_workspace_result) result["WorkspacesListFilesResult"] = to_class(WorkspacesListFilesResult, self.workspaces_list_files_result) @@ -6004,10 +6628,10 @@ def __init__(self, client: "JsonRpcClient", session_id: str): self._client = client self._session_id = session_id - async def handle_pending_tool_call(self, params: ToolsHandlePendingToolCallRequest, *, timeout: float | None = None) -> HandleToolCallResult: + async def handle_pending_tool_call(self, params: HandlePendingToolCallRequest, *, timeout: float | None = None) -> HandlePendingToolCallResult: params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} params_dict["sessionId"] = self._session_id - return HandleToolCallResult.from_dict(await self._client.request("session.tools.handlePendingToolCall", params_dict, **_timeout_kwargs(timeout))) + return HandlePendingToolCallResult.from_dict(await self._client.request("session.tools.handlePendingToolCall", params_dict, **_timeout_kwargs(timeout))) class CommandsApi: diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py index 9c0655c71..0cdd5eab0 100644 --- a/python/copilot/generated/session_events.py +++ b/python/copilot/generated/session_events.py @@ -307,6 +307,7 @@ class AssistantMessageData: reasoning_text: str | None = None request_id: str | None = None tool_requests: list[AssistantMessageToolRequest] | None = None + turn_id: str | None = None @staticmethod def from_dict(obj: Any) -> "AssistantMessageData": @@ -322,6 +323,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": reasoning_text = from_union([from_none, from_str], obj.get("reasoningText")) request_id = from_union([from_none, from_str], obj.get("requestId")) tool_requests = from_union([from_none, lambda x: from_list(AssistantMessageToolRequest.from_dict, x)], obj.get("toolRequests")) + turn_id = from_union([from_none, from_str], obj.get("turnId")) return AssistantMessageData( content=content, message_id=message_id, @@ -334,6 +336,7 @@ def from_dict(obj: Any) -> "AssistantMessageData": reasoning_text=reasoning_text, request_id=request_id, tool_requests=tool_requests, + turn_id=turn_id, ) def to_dict(self) -> dict: @@ -358,6 +361,8 @@ def to_dict(self) -> dict: result["requestId"] = from_union([from_none, from_str], self.request_id) if self.tool_requests is not None: result["toolRequests"] = from_union([from_none, lambda x: from_list(lambda x: to_class(AssistantMessageToolRequest, x), x)], self.tool_requests) + if self.turn_id is not None: + result["turnId"] = from_union([from_none, from_str], self.turn_id) return result @@ -1673,14 +1678,14 @@ def to_dict(self) -> dict: class PermissionCompletedData: "Permission request completion notification signaling UI dismissal" request_id: str - result: PermissionCompletedResult + result: PermissionResult tool_call_id: str | None = None @staticmethod def from_dict(obj: Any) -> "PermissionCompletedData": assert isinstance(obj, dict) request_id = from_str(obj.get("requestId")) - result = PermissionCompletedResult.from_dict(obj.get("result")) + result = PermissionResult.from_dict(obj.get("result")) tool_call_id = from_union([from_none, from_str], obj.get("toolCallId")) return PermissionCompletedData( request_id=request_id, @@ -1691,31 +1696,12 @@ def from_dict(obj: Any) -> "PermissionCompletedData": def to_dict(self) -> dict: result: dict = {} result["requestId"] = from_str(self.request_id) - result["result"] = to_class(PermissionCompletedResult, self.result) + result["result"] = to_class(PermissionResult, self.result) if self.tool_call_id is not None: result["toolCallId"] = from_union([from_none, from_str], self.tool_call_id) return result -@dataclass -class PermissionCompletedResult: - "The result of the permission request" - kind: PermissionCompletedKind - - @staticmethod - def from_dict(obj: Any) -> "PermissionCompletedResult": - assert isinstance(obj, dict) - kind = parse_enum(PermissionCompletedKind, obj.get("kind")) - return PermissionCompletedResult( - kind=kind, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["kind"] = to_enum(PermissionCompletedKind, self.kind) - return result - - @dataclass class PermissionPromptRequest: "Derived user-facing permission prompt details for UI consumers" @@ -2097,6 +2083,92 @@ def to_dict(self) -> dict: return result +@dataclass +class PermissionResult: + "The result of the permission request" + kind: PermissionResultKind + approval: UserToolSessionApproval | None = None + feedback: str | None = None + force_reject: bool | None = None + interrupt: bool | None = None + location_key: str | None = None + message: str | None = None + path: str | None = None + reason: str | None = None + rules: list[PermissionRule] | None = None + + @staticmethod + def from_dict(obj: Any) -> "PermissionResult": + assert isinstance(obj, dict) + kind = parse_enum(PermissionResultKind, obj.get("kind")) + approval = from_union([from_none, UserToolSessionApproval.from_dict], obj.get("approval")) + feedback = from_union([from_none, from_str], obj.get("feedback")) + force_reject = from_union([from_none, from_bool], obj.get("forceReject")) + interrupt = from_union([from_none, from_bool], obj.get("interrupt")) + location_key = from_union([from_none, from_str], obj.get("locationKey")) + message = from_union([from_none, from_str], obj.get("message")) + path = from_union([from_none, from_str], obj.get("path")) + reason = from_union([from_none, from_str], obj.get("reason")) + rules = from_union([from_none, lambda x: from_list(PermissionRule.from_dict, x)], obj.get("rules")) + return PermissionResult( + kind=kind, + approval=approval, + feedback=feedback, + force_reject=force_reject, + interrupt=interrupt, + location_key=location_key, + message=message, + path=path, + reason=reason, + rules=rules, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(PermissionResultKind, self.kind) + if self.approval is not None: + result["approval"] = from_union([from_none, lambda x: to_class(UserToolSessionApproval, x)], self.approval) + if self.feedback is not None: + result["feedback"] = from_union([from_none, from_str], self.feedback) + if self.force_reject is not None: + result["forceReject"] = from_union([from_none, from_bool], self.force_reject) + if self.interrupt is not None: + result["interrupt"] = from_union([from_none, from_bool], self.interrupt) + if self.location_key is not None: + result["locationKey"] = from_union([from_none, from_str], self.location_key) + if self.message is not None: + result["message"] = from_union([from_none, from_str], self.message) + if self.path is not None: + result["path"] = from_union([from_none, from_str], self.path) + if self.reason is not None: + result["reason"] = from_union([from_none, from_str], self.reason) + if self.rules is not None: + result["rules"] = from_union([from_none, lambda x: from_list(lambda x: to_class(PermissionRule, x), x)], self.rules) + return result + + +@dataclass +class PermissionRule: + argument: str | None + kind: str + + @staticmethod + def from_dict(obj: Any) -> "PermissionRule": + assert isinstance(obj, dict) + argument = from_union([from_none, from_str], obj.get("argument")) + kind = from_str(obj.get("kind")) + return PermissionRule( + argument=argument, + kind=kind, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["argument"] = from_union([from_none, from_str], self.argument) + result["kind"] = from_str(self.kind) + return result + + @dataclass class SamplingCompletedData: "Sampling request completion notification signaling UI dismissal" @@ -2672,9 +2744,11 @@ class SessionResumeData: resume_time: datetime already_in_use: bool | None = None context: WorkingDirectoryContext | None = None + continue_pending_work: bool | None = None reasoning_effort: str | None = None remote_steerable: bool | None = None selected_model: str | None = None + session_was_active: bool | None = None @staticmethod def from_dict(obj: Any) -> "SessionResumeData": @@ -2683,17 +2757,21 @@ def from_dict(obj: Any) -> "SessionResumeData": resume_time = from_datetime(obj.get("resumeTime")) already_in_use = from_union([from_none, from_bool], obj.get("alreadyInUse")) context = from_union([from_none, WorkingDirectoryContext.from_dict], obj.get("context")) + continue_pending_work = from_union([from_none, from_bool], obj.get("continuePendingWork")) reasoning_effort = from_union([from_none, from_str], obj.get("reasoningEffort")) remote_steerable = from_union([from_none, from_bool], obj.get("remoteSteerable")) selected_model = from_union([from_none, from_str], obj.get("selectedModel")) + session_was_active = from_union([from_none, from_bool], obj.get("sessionWasActive")) return SessionResumeData( event_count=event_count, resume_time=resume_time, already_in_use=already_in_use, context=context, + continue_pending_work=continue_pending_work, reasoning_effort=reasoning_effort, remote_steerable=remote_steerable, selected_model=selected_model, + session_was_active=session_was_active, ) def to_dict(self) -> dict: @@ -2704,12 +2782,16 @@ def to_dict(self) -> dict: result["alreadyInUse"] = from_union([from_none, from_bool], self.already_in_use) if self.context is not None: result["context"] = from_union([from_none, lambda x: to_class(WorkingDirectoryContext, x)], self.context) + if self.continue_pending_work is not None: + result["continuePendingWork"] = from_union([from_none, from_bool], self.continue_pending_work) if self.reasoning_effort is not None: result["reasoningEffort"] = from_union([from_none, from_str], self.reasoning_effort) if self.remote_steerable is not None: result["remoteSteerable"] = from_union([from_none, from_bool], self.remote_steerable) if self.selected_model is not None: result["selectedModel"] = from_union([from_none, from_str], self.selected_model) + if self.session_was_active is not None: + result["sessionWasActive"] = from_union([from_none, from_bool], self.session_was_active) return result @@ -2727,7 +2809,9 @@ class SessionShutdownData: current_tokens: float | None = None error_reason: str | None = None system_tokens: float | None = None + token_details: dict[str, ShutdownTokenDetail] | None = None tool_definitions_tokens: float | None = None + total_nano_aiu: float | None = None @staticmethod def from_dict(obj: Any) -> "SessionShutdownData": @@ -2743,7 +2827,9 @@ def from_dict(obj: Any) -> "SessionShutdownData": current_tokens = from_union([from_none, from_float], obj.get("currentTokens")) error_reason = from_union([from_none, from_str], obj.get("errorReason")) system_tokens = from_union([from_none, from_float], obj.get("systemTokens")) + token_details = from_union([from_none, lambda x: from_dict(ShutdownTokenDetail.from_dict, x)], obj.get("tokenDetails")) tool_definitions_tokens = from_union([from_none, from_float], obj.get("toolDefinitionsTokens")) + total_nano_aiu = from_union([from_none, from_float], obj.get("totalNanoAiu")) return SessionShutdownData( code_changes=code_changes, model_metrics=model_metrics, @@ -2756,7 +2842,9 @@ def from_dict(obj: Any) -> "SessionShutdownData": current_tokens=current_tokens, error_reason=error_reason, system_tokens=system_tokens, + token_details=token_details, tool_definitions_tokens=tool_definitions_tokens, + total_nano_aiu=total_nano_aiu, ) def to_dict(self) -> dict: @@ -2777,8 +2865,12 @@ def to_dict(self) -> dict: result["errorReason"] = from_union([from_none, from_str], self.error_reason) if self.system_tokens is not None: result["systemTokens"] = from_union([from_none, to_float], self.system_tokens) + if self.token_details is not None: + result["tokenDetails"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(ShutdownTokenDetail, x), x)], self.token_details) if self.tool_definitions_tokens is not None: result["toolDefinitionsTokens"] = from_union([from_none, to_float], self.tool_definitions_tokens) + if self.total_nano_aiu is not None: + result["totalNanoAiu"] = from_union([from_none, to_float], self.total_nano_aiu) return result @@ -3121,21 +3213,31 @@ def to_dict(self) -> dict: class ShutdownModelMetric: requests: ShutdownModelMetricRequests usage: ShutdownModelMetricUsage + token_details: dict[str, ShutdownModelMetricTokenDetail] | None = None + total_nano_aiu: float | None = None @staticmethod def from_dict(obj: Any) -> "ShutdownModelMetric": assert isinstance(obj, dict) requests = ShutdownModelMetricRequests.from_dict(obj.get("requests")) usage = ShutdownModelMetricUsage.from_dict(obj.get("usage")) + token_details = from_union([from_none, lambda x: from_dict(ShutdownModelMetricTokenDetail.from_dict, x)], obj.get("tokenDetails")) + total_nano_aiu = from_union([from_none, from_float], obj.get("totalNanoAiu")) return ShutdownModelMetric( requests=requests, usage=usage, + token_details=token_details, + total_nano_aiu=total_nano_aiu, ) def to_dict(self) -> dict: result: dict = {} result["requests"] = to_class(ShutdownModelMetricRequests, self.requests) result["usage"] = to_class(ShutdownModelMetricUsage, self.usage) + if self.token_details is not None: + result["tokenDetails"] = from_union([from_none, lambda x: from_dict(lambda x: to_class(ShutdownModelMetricTokenDetail, x), x)], self.token_details) + if self.total_nano_aiu is not None: + result["totalNanoAiu"] = from_union([from_none, to_float], self.total_nano_aiu) return result @@ -3162,6 +3264,24 @@ def to_dict(self) -> dict: return result +@dataclass +class ShutdownModelMetricTokenDetail: + token_count: float + + @staticmethod + def from_dict(obj: Any) -> "ShutdownModelMetricTokenDetail": + assert isinstance(obj, dict) + token_count = from_float(obj.get("tokenCount")) + return ShutdownModelMetricTokenDetail( + token_count=token_count, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["tokenCount"] = to_float(self.token_count) + return result + + @dataclass class ShutdownModelMetricUsage: "Token usage breakdown" @@ -3198,6 +3318,24 @@ def to_dict(self) -> dict: return result +@dataclass +class ShutdownTokenDetail: + token_count: float + + @staticmethod + def from_dict(obj: Any) -> "ShutdownTokenDetail": + assert isinstance(obj, dict) + token_count = from_float(obj.get("tokenCount")) + return ShutdownTokenDetail( + token_count=token_count, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["tokenCount"] = to_float(self.token_count) + return result + + @dataclass class SkillInvokedData: "Skill invocation details including content, allowed tools, and plugin metadata" @@ -3748,6 +3886,7 @@ class ToolExecutionCompleteData: parent_tool_call_id: str | None = None result: ToolExecutionCompleteResult | None = None tool_telemetry: dict[str, Any] | None = None + turn_id: str | None = None @staticmethod def from_dict(obj: Any) -> "ToolExecutionCompleteData": @@ -3761,6 +3900,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) result = from_union([from_none, ToolExecutionCompleteResult.from_dict], obj.get("result")) tool_telemetry = from_union([from_none, lambda x: from_dict(lambda x: x, x)], obj.get("toolTelemetry")) + turn_id = from_union([from_none, from_str], obj.get("turnId")) return ToolExecutionCompleteData( success=success, tool_call_id=tool_call_id, @@ -3771,6 +3911,7 @@ def from_dict(obj: Any) -> "ToolExecutionCompleteData": parent_tool_call_id=parent_tool_call_id, result=result, tool_telemetry=tool_telemetry, + turn_id=turn_id, ) def to_dict(self) -> dict: @@ -3791,6 +3932,8 @@ def to_dict(self) -> dict: result["result"] = from_union([from_none, lambda x: to_class(ToolExecutionCompleteResult, x)], self.result) if self.tool_telemetry is not None: result["toolTelemetry"] = from_union([from_none, lambda x: from_dict(lambda x: x, x)], self.tool_telemetry) + if self.turn_id is not None: + result["turnId"] = from_union([from_none, from_str], self.turn_id) return result @@ -3903,6 +4046,7 @@ class ToolExecutionStartData: mcp_tool_name: str | None = None # Deprecated: this field is deprecated. parent_tool_call_id: str | None = None + turn_id: str | None = None @staticmethod def from_dict(obj: Any) -> "ToolExecutionStartData": @@ -3913,6 +4057,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": mcp_server_name = from_union([from_none, from_str], obj.get("mcpServerName")) mcp_tool_name = from_union([from_none, from_str], obj.get("mcpToolName")) parent_tool_call_id = from_union([from_none, from_str], obj.get("parentToolCallId")) + turn_id = from_union([from_none, from_str], obj.get("turnId")) return ToolExecutionStartData( tool_call_id=tool_call_id, tool_name=tool_name, @@ -3920,6 +4065,7 @@ def from_dict(obj: Any) -> "ToolExecutionStartData": mcp_server_name=mcp_server_name, mcp_tool_name=mcp_tool_name, parent_tool_call_id=parent_tool_call_id, + turn_id=turn_id, ) def to_dict(self) -> dict: @@ -3934,6 +4080,8 @@ def to_dict(self) -> dict: result["mcpToolName"] = from_union([from_none, from_str], self.mcp_tool_name) if self.parent_tool_call_id is not None: result["parentToolCallId"] = from_union([from_none, from_str], self.parent_tool_call_id) + if self.turn_id is not None: + result["turnId"] = from_union([from_none, from_str], self.turn_id) return result @@ -4215,6 +4363,7 @@ class UserMessageData: attachments: list[UserMessageAttachment] | None = None interaction_id: str | None = None native_document_path_fallback_paths: list[str] | None = None + parent_agent_task_id: str | None = None source: str | None = None supported_native_document_mime_types: list[str] | None = None transformed_content: str | None = None @@ -4227,6 +4376,7 @@ def from_dict(obj: Any) -> "UserMessageData": attachments = from_union([from_none, lambda x: from_list(UserMessageAttachment.from_dict, x)], obj.get("attachments")) interaction_id = from_union([from_none, from_str], obj.get("interactionId")) native_document_path_fallback_paths = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("nativeDocumentPathFallbackPaths")) + parent_agent_task_id = from_union([from_none, from_str], obj.get("parentAgentTaskId")) source = from_union([from_none, from_str], obj.get("source")) supported_native_document_mime_types = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("supportedNativeDocumentMimeTypes")) transformed_content = from_union([from_none, from_str], obj.get("transformedContent")) @@ -4236,6 +4386,7 @@ def from_dict(obj: Any) -> "UserMessageData": attachments=attachments, interaction_id=interaction_id, native_document_path_fallback_paths=native_document_path_fallback_paths, + parent_agent_task_id=parent_agent_task_id, source=source, supported_native_document_mime_types=supported_native_document_mime_types, transformed_content=transformed_content, @@ -4252,6 +4403,8 @@ def to_dict(self) -> dict: result["interactionId"] = from_union([from_none, from_str], self.interaction_id) if self.native_document_path_fallback_paths is not None: result["nativeDocumentPathFallbackPaths"] = from_union([from_none, lambda x: from_list(from_str, x)], self.native_document_path_fallback_paths) + if self.parent_agent_task_id is not None: + result["parentAgentTaskId"] = from_union([from_none, from_str], self.parent_agent_task_id) if self.source is not None: result["source"] = from_union([from_none, from_str], self.source) if self.supported_native_document_mime_types is not None: @@ -4261,6 +4414,40 @@ def to_dict(self) -> dict: return result +@dataclass +class UserToolSessionApproval: + "The approval to add as a session-scoped rule" + kind: UserToolSessionApprovalKind + command_identifiers: list[str] | None = None + server_name: str | None = None + tool_name: str | None = None + + @staticmethod + def from_dict(obj: Any) -> "UserToolSessionApproval": + assert isinstance(obj, dict) + kind = parse_enum(UserToolSessionApprovalKind, obj.get("kind")) + command_identifiers = from_union([from_none, lambda x: from_list(from_str, x)], obj.get("commandIdentifiers")) + server_name = from_union([from_none, from_str], obj.get("serverName")) + tool_name = from_union([from_none, from_str], obj.get("toolName")) + return UserToolSessionApproval( + kind=kind, + command_identifiers=command_identifiers, + server_name=server_name, + tool_name=tool_name, + ) + + def to_dict(self) -> dict: + result: dict = {} + result["kind"] = to_enum(UserToolSessionApprovalKind, self.kind) + if self.command_identifiers is not None: + result["commandIdentifiers"] = from_union([from_none, lambda x: from_list(from_str, x)], self.command_identifiers) + if self.server_name is not None: + result["serverName"] = from_union([from_none, from_str], self.server_name) + if self.tool_name is not None: + result["toolName"] = from_union([from_none, from_str], self.tool_name) + return result + + @dataclass class WorkingDirectoryContext: "Working directory and git context at session start" @@ -4381,18 +4568,6 @@ class ModelCallFailureSource(Enum): MCP_SAMPLING = "mcp_sampling" -class PermissionCompletedKind(Enum): - "The outcome of the permission request" - APPROVED = "approved" - APPROVED_FOR_SESSION = "approved-for-session" - APPROVED_FOR_LOCATION = "approved-for-location" - DENIED_BY_RULES = "denied-by-rules" - DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" - DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" - DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" - DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" - - class PermissionPromptRequestKind(Enum): "Derived user-facing permission prompt details for UI consumers discriminator" COMMANDS = "commands" @@ -4449,6 +4624,19 @@ class PermissionRequestMemoryDirection(Enum): DOWNVOTE = "downvote" +class PermissionResultKind(Enum): + "The result of the permission request discriminator" + APPROVED = "approved" + APPROVED_FOR_SESSION = "approved-for-session" + APPROVED_FOR_LOCATION = "approved-for-location" + CANCELLED = "cancelled" + DENIED_BY_RULES = "denied-by-rules" + DENIED_NO_APPROVAL_RULE_AND_COULD_NOT_REQUEST_FROM_USER = "denied-no-approval-rule-and-could-not-request-from-user" + DENIED_INTERACTIVELY_BY_USER = "denied-interactively-by-user" + DENIED_BY_CONTENT_EXCLUSION_POLICY = "denied-by-content-exclusion-policy" + DENIED_BY_PERMISSION_REQUEST_HOOK = "denied-by-permission-request-hook" + + class PlanChangedOperation(Enum): "The type of operation performed on the plan file" CREATE = "create" @@ -4524,6 +4712,16 @@ class UserMessageAttachmentType(Enum): BLOB = "blob" +class UserToolSessionApprovalKind(Enum): + "The approval to add as a session-scoped rule discriminator" + COMMANDS = "commands" + READ = "read" + WRITE = "write" + MCP = "mcp" + MEMORY = "memory" + CUSTOM_TOOL = "custom-tool" + + class WorkingDirectoryContextHostType(Enum): "Hosting platform type of the repository (github or ado)" GITHUB = "github" diff --git a/python/copilot/session.py b/python/copilot/session.py index 3e1d40931..09f6b9cfc 100644 --- a/python/copilot/session.py +++ b/python/copilot/session.py @@ -24,6 +24,8 @@ from .generated.rpc import ( ClientSessionApiHandlers, CommandsHandlePendingCommandRequest, + ExternalToolTextResultForLlm, + HandlePendingToolCallRequest, LogRequest, ModelSwitchToRequest, PermissionDecision, @@ -31,8 +33,6 @@ PermissionDecisionRequest, SessionLogLevel, SessionRpc, - ToolCallResult, - ToolsHandlePendingToolCallRequest, UIElicitationRequest, UIElicitationResponse, UIElicitationResponseAction, @@ -962,6 +962,15 @@ class ResumeSessionConfig(TypedDict, total=False): # When True, skips emitting the session.resume event. # Useful for reconnecting to a session without triggering resume-related side effects. disable_resume: bool + # When True, instructs the runtime to continue any tool calls or permission prompts + # that were still pending when the session was last suspended. When False (the + # default), the runtime treats pending work as interrupted on resume. + # + # For permission requests, the runtime re-emits ``permission.requested`` so the + # registered ``on_permission_request`` handler can re-prompt; for external tool + # calls, the consumer is expected to supply the result via the corresponding + # low-level RPC method. + continue_pending_work: bool # Optional event handler registered before the session.resume RPC is issued, # ensuring early events are delivered. See SessionConfig.on_event. on_event: Callable[[SessionEvent], None] @@ -1403,16 +1412,16 @@ async def _execute_tool_and_respond( # failures send the full structured result to preserve metadata. if tool_result._from_exception: await self.rpc.tools.handle_pending_tool_call( - ToolsHandlePendingToolCallRequest( + HandlePendingToolCallRequest( request_id=request_id, error=tool_result.error, ) ) else: await self.rpc.tools.handle_pending_tool_call( - ToolsHandlePendingToolCallRequest( + HandlePendingToolCallRequest( request_id=request_id, - result=ToolCallResult( + result=ExternalToolTextResultForLlm( text_result_for_llm=tool_result.text_result_for_llm, error=tool_result.error, result_type=tool_result.result_type, @@ -1423,7 +1432,7 @@ async def _execute_tool_and_respond( except Exception as exc: try: await self.rpc.tools.handle_pending_tool_call( - ToolsHandlePendingToolCallRequest( + HandlePendingToolCallRequest( request_id=request_id, error=str(exc), ) diff --git a/python/e2e/test_commands.py b/python/e2e/test_commands.py index 6cc68e246..16fd1c7b7 100644 --- a/python/e2e/test_commands.py +++ b/python/e2e/test_commands.py @@ -7,6 +7,7 @@ """ import asyncio +import contextlib import os import shutil import tempfile @@ -106,7 +107,8 @@ async def configure_for_test(self, test_file: str, test_name: str): if item.is_dir(): shutil.rmtree(item, ignore_errors=True) else: - item.unlink(missing_ok=True) + with contextlib.suppress(OSError): + item.unlink(missing_ok=True) def _get_env(self) -> dict: env = os.environ.copy() diff --git a/python/e2e/test_multi_client.py b/python/e2e/test_multi_client.py index 386f2eeb0..bf7fba44d 100644 --- a/python/e2e/test_multi_client.py +++ b/python/e2e/test_multi_client.py @@ -5,6 +5,7 @@ """ import asyncio +import contextlib import os import shutil import tempfile @@ -110,19 +111,17 @@ async def configure_for_test(self, test_file: str, test_name: str): if self._proxy: await self._proxy.configure(abs_snapshot_path, self.work_dir) - # Clear temp directories between tests + # Clear temp directories between tests; tolerate Windows holding the + # SQLite session-store.db open briefly after the CLI subprocess exits. from pathlib import Path - for item in Path(self.home_dir).iterdir(): - if item.is_dir(): - shutil.rmtree(item, ignore_errors=True) - else: - item.unlink(missing_ok=True) - for item in Path(self.work_dir).iterdir(): - if item.is_dir(): - shutil.rmtree(item, ignore_errors=True) - else: - item.unlink(missing_ok=True) + for base_dir in (self.home_dir, self.work_dir): + for item in Path(base_dir).iterdir(): + if item.is_dir(): + shutil.rmtree(item, ignore_errors=True) + else: + with contextlib.suppress(OSError): + item.unlink(missing_ok=True) def get_env(self) -> dict: env = os.environ.copy() diff --git a/python/e2e/test_ui_elicitation_multi_client.py b/python/e2e/test_ui_elicitation_multi_client.py index e12202b2f..e771107fb 100644 --- a/python/e2e/test_ui_elicitation_multi_client.py +++ b/python/e2e/test_ui_elicitation_multi_client.py @@ -8,6 +8,7 @@ """ import asyncio +import contextlib import os import shutil import tempfile @@ -113,7 +114,8 @@ async def configure_for_test(self, test_file: str, test_name: str): if item.is_dir(): shutil.rmtree(item, ignore_errors=True) else: - item.unlink(missing_ok=True) + with contextlib.suppress(OSError): + item.unlink(missing_ok=True) def _get_env(self) -> dict: env = os.environ.copy() diff --git a/python/e2e/testharness/context.py b/python/e2e/testharness/context.py index 09a279d0d..514834522 100644 --- a/python/e2e/testharness/context.py +++ b/python/e2e/testharness/context.py @@ -4,6 +4,7 @@ Provides isolated directories and a replaying proxy for testing the SDK. """ +import contextlib import os import re import shutil @@ -113,18 +114,16 @@ async def configure_for_test(self, test_file: str, test_name: str): await self._proxy.configure(abs_snapshot_path, self.work_dir) # Clear temp directories between tests (but leave them in place) - # Use ignore_errors=True to handle race conditions where files may still - # be written by background processes during cleanup - for item in Path(self.home_dir).iterdir(): - if item.is_dir(): - shutil.rmtree(item, ignore_errors=True) - else: - item.unlink(missing_ok=True) - for item in Path(self.work_dir).iterdir(): - if item.is_dir(): - shutil.rmtree(item, ignore_errors=True) - else: - item.unlink(missing_ok=True) + # Use ignore_errors=True / suppress(OSError) to handle race conditions + # where files (e.g., SQLite session-store.db on Windows) may still be + # held open by a background process during cleanup. + for base_dir in (self.home_dir, self.work_dir): + for item in Path(base_dir).iterdir(): + if item.is_dir(): + shutil.rmtree(item, ignore_errors=True) + else: + with contextlib.suppress(OSError): + item.unlink(missing_ok=True) def get_env(self) -> dict: """Return environment variables configured for isolated testing.""" diff --git a/python/test_client.py b/python/test_client.py index ac1b735bf..1f038c99b 100644 --- a/python/test_client.py +++ b/python/test_client.py @@ -716,6 +716,63 @@ async def mock_request(method, params): finally: await client.force_stop() + @pytest.mark.asyncio + async def test_resume_session_forwards_continue_pending_work(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured: dict = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + continue_pending_work=True, + ) + assert captured["session.resume"]["continuePendingWork"] is True + finally: + await client.force_stop() + + @pytest.mark.asyncio + async def test_resume_session_omits_continue_pending_work_by_default(self): + client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) + await client.start() + + try: + session = await client.create_session( + on_permission_request=PermissionHandler.approve_all + ) + + captured: dict = {} + original_request = client._client.request + + async def mock_request(method, params): + captured[method] = params + if method == "session.resume": + return {"sessionId": session.session_id} + return await original_request(method, params) + + client._client.request = mock_request + await client.resume_session( + session.session_id, + on_permission_request=PermissionHandler.approve_all, + ) + assert "continuePendingWork" not in captured["session.resume"] + finally: + await client.force_stop() + @pytest.mark.asyncio async def test_set_model_sends_correct_rpc(self): client = CopilotClient(SubprocessConfig(cli_path=CLI_PATH)) diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json index 8b8db1111..d4f646085 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.40-0", + "@github/copilot": "^1.0.40-1", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0", @@ -462,27 +462,27 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.40-0.tgz", - "integrity": "sha512-KIrRqPOCGPcYUq09wvi66qUi5YMFTH5z9fOEzo1BuyLFVQqUen0TtRk0mpbhG6TxArLPqosBY8nDXOdc+NqKKw==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.40-1.tgz", + "integrity": "sha512-jdohe7CcjlmbNR0bnL/uAbaYDtmqZnHcHo3t/ZspVb9vo7+yk7AdTc4AF4TpV7M/tCKc+ynoqgQNCalWAmXYWQ==", "dev": true, "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.40-0", - "@github/copilot-darwin-x64": "1.0.40-0", - "@github/copilot-linux-arm64": "1.0.40-0", - "@github/copilot-linux-x64": "1.0.40-0", - "@github/copilot-win32-arm64": "1.0.40-0", - "@github/copilot-win32-x64": "1.0.40-0" + "@github/copilot-darwin-arm64": "1.0.40-1", + "@github/copilot-darwin-x64": "1.0.40-1", + "@github/copilot-linux-arm64": "1.0.40-1", + "@github/copilot-linux-x64": "1.0.40-1", + "@github/copilot-win32-arm64": "1.0.40-1", + "@github/copilot-win32-x64": "1.0.40-1" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.40-0.tgz", - "integrity": "sha512-l905DiMvOB7Jn5MAkS+iEQcM99hmJHQ5yrKaGJ9OteVWBCcxPEO5ctnRYFdeq4NHL+9OnAzJzNc0kwScOAUCzg==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.40-1.tgz", + "integrity": "sha512-DSArBmv1A6BstJs23QgXzes7B8Hu+sz4Ccqh1NhY/4NPfxck1rD2v61ZWz8kdwpgTdjP6lWSZ5nLWdi9Go+PhA==", "cpu": [ "arm64" ], @@ -497,9 +497,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.40-0.tgz", - "integrity": "sha512-e/Dtc1CjZ5SpNLdtY7vHxzH3hhNKfiMJdyp/2UY/6rzXsSPfbw9xZL01nUBbZt0aYj2FbPBDMTyfqoh5weUSww==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.40-1.tgz", + "integrity": "sha512-fTOL2XChDSsPc/q//mIFlq47ABNgyUKqz1+ix5oxloE9RlT1YpD7v3O+dqU/tmdxXwMTfQqgZsYglRY/nna3yw==", "cpu": [ "x64" ], @@ -514,9 +514,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.40-0.tgz", - "integrity": "sha512-fR/gVUXFpjwojNf3Pg5lH2vrb8q0Gghv6RK6xx1QS6pEBdPTMGeoPxQVqSSB6dZCR/X3dkK1tIZ5IGnuABDHbA==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.40-1.tgz", + "integrity": "sha512-+aon/9kAxgGlxLZrxzwUrNO1G/eUIhhEB6W4u1sMs2GwXjANrDr6WIsp056dOnU2TY9oEnn8vWTJQSIwyFlGHQ==", "cpu": [ "arm64" ], @@ -531,9 +531,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.40-0.tgz", - "integrity": "sha512-vDfE5Cxgg+BealbxBjqa0MUrLGJF+zT+XygMFgWXi/4ESVpRvvAet8rUoLeL+7Lgg6QRlS+UMPWfWL6ZAoSp5w==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.40-1.tgz", + "integrity": "sha512-p44TeuI01EpizsJmQX0W21f7pPXyTvrXYkv/5W1GgCiNEP4HKLG9rdgdIEtqaIMnXFKssxpGF45+hJuz0iep5g==", "cpu": [ "x64" ], @@ -548,9 +548,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.40-0.tgz", - "integrity": "sha512-7aEo1CZ5476lWRtfcym0TX1DhH5l9+PKENHPOr8vfziHKeNlWYkXHVTo7OGIUnCAwIetbCQRyf92nnaFhG/8Jw==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.40-1.tgz", + "integrity": "sha512-nrbMh1qc8VWL1KU2N+VdQ2M+4jiGlugApIFNUbfywnUITktWEpQ62Jqf2YQlrMr51f1F1fjSE/YBuDW6InzzYg==", "cpu": [ "arm64" ], @@ -565,9 +565,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.40-0", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.40-0.tgz", - "integrity": "sha512-egnoilEO6TS0qSexe+A26GUSyfeinaqXJRTYL4Qr6eVgAfFhCEpCJtK/gHIZmlDXnq2ex3jGrVvz9+ZIp+JSoA==", + "version": "1.0.40-1", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.40-1.tgz", + "integrity": "sha512-n5qmzAn4OKbdHJeOmdt4eUA7XWSUOmNSJUtRswKVqgCxrmMOR/0FXCPzVS0gAJn+UEj9ApHulC6G09HR5Nhp7Q==", "cpu": [ "x64" ], diff --git a/test/harness/package.json b/test/harness/package.json index 15b1aa940..7bca45eaa 100644 --- a/test/harness/package.json +++ b/test/harness/package.json @@ -11,7 +11,7 @@ "test": "vitest run" }, "devDependencies": { - "@github/copilot": "^1.0.40-0", + "@github/copilot": "^1.0.40-1", "@modelcontextprotocol/sdk": "^1.26.0", "@types/node": "^25.3.3", "openai": "^6.17.0",