diff --git a/.github/workflows/rust-sdk-tests.yml b/.github/workflows/rust-sdk-tests.yml
index f542307be..393db2d18 100644
--- a/.github/workflows/rust-sdk-tests.yml
+++ b/.github/workflows/rust-sdk-tests.yml
@@ -70,6 +70,8 @@ jobs:
- uses: Swatinem/rust-cache@v2
with:
workspaces: "rust"
+ prefix-key: v1-rust-no-bin
+ cache-bin: false
- name: cargo fmt --check (nightly)
if: runner.os == 'Linux'
@@ -135,6 +137,8 @@ jobs:
with:
workspaces: "rust"
key: bundled-cli
+ prefix-key: v1-rust-no-bin
+ cache-bin: false
- name: Read pinned @github/copilot CLI version
id: cli-version
diff --git a/dotnet/src/Generated/Rpc.cs b/dotnet/src/Generated/Rpc.cs
index d51dff03e..efed73c1d 100644
--- a/dotnet/src/Generated/Rpc.cs
+++ b/dotnet/src/Generated/Rpc.cs
@@ -65,12 +65,36 @@ internal sealed class ConnectRequest
public string? Token { get; set; }
}
+/// Token-level pricing information for this model.
+public sealed class ModelBillingTokenPrices
+{
+ /// Number of tokens per standard billing batch.
+ [JsonPropertyName("batchSize")]
+ public long? BatchSize { get; set; }
+
+ /// Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD).
+ [JsonPropertyName("cachePrice")]
+ public long? CachePrice { get; set; }
+
+ /// Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD).
+ [JsonPropertyName("inputPrice")]
+ public long? InputPrice { get; set; }
+
+ /// Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD).
+ [JsonPropertyName("outputPrice")]
+ public long? OutputPrice { get; set; }
+}
+
/// Billing information.
public sealed class ModelBilling
{
/// Billing cost multiplier relative to the base rate.
[JsonPropertyName("multiplier")]
public double? Multiplier { get; set; }
+
+ /// Token-level pricing information for this model.
+ [JsonPropertyName("tokenPrices")]
+ public ModelBillingTokenPrices? TokenPrices { get; set; }
}
/// Vision-specific limits.
@@ -492,6 +516,10 @@ internal sealed class SessionFsSetProviderRequest
[Experimental(Diagnostics.Experimental)]
public sealed class SessionsForkResult
{
+ /// Friendly name assigned to the forked session, if any.
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
/// The new forked session's ID.
[JsonPropertyName("sessionId")]
public string SessionId { get; set; } = string.Empty;
@@ -501,6 +529,10 @@ public sealed class SessionsForkResult
[Experimental(Diagnostics.Experimental)]
internal sealed class SessionsForkRequest
{
+ /// Optional friendly name to assign to the forked session.
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
/// Source session ID to fork from.
[JsonPropertyName("sessionId")]
public string SessionId { get; set; } = string.Empty;
@@ -1494,6 +1526,19 @@ internal sealed class SkillsDisableRequest
public string SessionId { get; set; } = string.Empty;
}
+/// RPC data type for SkillsLoadDiagnostics operations.
+[Experimental(Diagnostics.Experimental)]
+public sealed class SkillsLoadDiagnostics
+{
+ /// Errors emitted while loading skills (e.g. skills that failed to load entirely).
+ [JsonPropertyName("errors")]
+ public IList Errors { get => field ??= []; set; }
+
+ /// Warnings emitted while loading skills (e.g. skills that loaded but had issues).
+ [JsonPropertyName("warnings")]
+ public IList Warnings { get => field ??= []; set; }
+}
+
/// RPC data type for SessionSkillsReload operations.
[Experimental(Diagnostics.Experimental)]
internal sealed class SessionSkillsReloadRequest
@@ -1765,6 +1810,204 @@ internal sealed class HandlePendingToolCallRequest
public string SessionId { get; set; } = string.Empty;
}
+/// Optional unstructured input hint.
+public sealed class SlashCommandInput
+{
+ /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion).
+ [JsonPropertyName("completion")]
+ public SlashCommandInputCompletion? Completion { get; set; }
+
+ /// Hint to display when command input has not been provided.
+ [JsonPropertyName("hint")]
+ public string Hint { get; set; } = string.Empty;
+
+ /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace.
+ [JsonPropertyName("preserveMultilineInput")]
+ public bool? PreserveMultilineInput { get; set; }
+
+ /// When true, the command requires non-empty input; clients should render the input hint as required.
+ [JsonPropertyName("required")]
+ public bool? Required { get; set; }
+}
+
+/// RPC data type for SlashCommandInfo operations.
+public sealed class SlashCommandInfo
+{
+ /// Canonical aliases without leading slashes.
+ [JsonPropertyName("aliases")]
+ public IList? Aliases { get; set; }
+
+ /// Whether the command may run while an agent turn is active.
+ [JsonPropertyName("allowDuringAgentExecution")]
+ public bool AllowDuringAgentExecution { get; set; }
+
+ /// Human-readable command description.
+ [JsonPropertyName("description")]
+ public string Description { get; set; } = string.Empty;
+
+ /// Whether the command is experimental.
+ [JsonPropertyName("experimental")]
+ public bool? Experimental { get; set; }
+
+ /// Optional unstructured input hint.
+ [JsonPropertyName("input")]
+ public SlashCommandInput? Input { get; set; }
+
+ /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command.
+ [JsonPropertyName("kind")]
+ public SlashCommandKind Kind { get; set; }
+
+ /// Canonical command name without a leading slash.
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+}
+
+/// RPC data type for CommandList operations.
+public sealed class CommandList
+{
+ /// Commands available in this session.
+ [JsonPropertyName("commands")]
+ public IList Commands { get => field ??= []; set; }
+}
+
+/// RPC data type for CommandsList operations.
+public sealed class CommandsListRequest
+{
+ /// Include runtime built-in commands.
+ [JsonPropertyName("includeBuiltins")]
+ public bool? IncludeBuiltins { get; set; }
+
+ /// Include commands registered by protocol clients, including SDK clients and extensions.
+ [JsonPropertyName("includeClientCommands")]
+ public bool? IncludeClientCommands { get; set; }
+
+ /// Include enabled user-invocable skills and commands.
+ [JsonPropertyName("includeSkills")]
+ public bool? IncludeSkills { get; set; }
+}
+
+/// RPC data type for CommandsListRequestWithSession operations.
+internal sealed class CommandsListRequestWithSession
+{
+ /// Include runtime built-in commands.
+ [JsonPropertyName("includeBuiltins")]
+ public bool? IncludeBuiltins { get; set; }
+
+ /// Include commands registered by protocol clients, including SDK clients and extensions.
+ [JsonPropertyName("includeClientCommands")]
+ public bool? IncludeClientCommands { get; set; }
+
+ /// Include enabled user-invocable skills and commands.
+ [JsonPropertyName("includeSkills")]
+ public bool? IncludeSkills { get; set; }
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
+/// Polymorphic base type discriminated by kind.
+[JsonPolymorphic(
+ TypeDiscriminatorPropertyName = "kind",
+ UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
+[JsonDerivedType(typeof(SlashCommandInvocationResultText), "text")]
+[JsonDerivedType(typeof(SlashCommandInvocationResultAgentPrompt), "agent-prompt")]
+[JsonDerivedType(typeof(SlashCommandInvocationResultCompleted), "completed")]
+public partial class SlashCommandInvocationResult
+{
+ /// The type discriminator.
+ [JsonPropertyName("kind")]
+ public virtual string Kind { get; set; } = string.Empty;
+}
+
+
+/// The text variant of .
+public partial class SlashCommandInvocationResultText : SlashCommandInvocationResult
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "text";
+
+ /// Whether text contains Markdown.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("markdown")]
+ public bool? Markdown { get; set; }
+
+ /// Whether ANSI sequences should be preserved.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("preserveAnsi")]
+ public bool? PreserveAnsi { get; set; }
+
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("runtimeSettingsChanged")]
+ public bool? RuntimeSettingsChanged { get; set; }
+
+ /// Text output for the client to render.
+ [JsonPropertyName("text")]
+ public required string Text { get; set; }
+}
+
+/// The agent-prompt variant of .
+public partial class SlashCommandInvocationResultAgentPrompt : SlashCommandInvocationResult
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "agent-prompt";
+
+ /// Prompt text to display to the user.
+ [JsonPropertyName("displayPrompt")]
+ public required string DisplayPrompt { get; set; }
+
+ /// Optional target session mode.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("mode")]
+ public SlashCommandAgentPromptMode? Mode { get; set; }
+
+ /// Prompt to submit to the agent.
+ [JsonPropertyName("prompt")]
+ public required string Prompt { get; set; }
+
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("runtimeSettingsChanged")]
+ public bool? RuntimeSettingsChanged { get; set; }
+}
+
+/// The completed variant of .
+public partial class SlashCommandInvocationResultCompleted : SlashCommandInvocationResult
+{
+ ///
+ [JsonIgnore]
+ public override string Kind => "completed";
+
+ /// Optional user-facing message describing the completed command.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("message")]
+ public string? Message { get; set; }
+
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("runtimeSettingsChanged")]
+ public bool? RuntimeSettingsChanged { get; set; }
+}
+
+/// RPC data type for CommandsInvoke operations.
+internal sealed class CommandsInvokeRequest
+{
+ /// Raw input after the command name.
+ [JsonPropertyName("input")]
+ public string? Input { get; set; }
+
+ /// Command name. Leading slashes are stripped and the name is matched case-insensitively.
+ [JsonPropertyName("name")]
+ public string Name { get; set; } = string.Empty;
+
+ /// Target session identifier.
+ [JsonPropertyName("sessionId")]
+ public string SessionId { get; set; } = string.Empty;
+}
+
/// RPC data type for CommandsHandlePendingCommand operations.
public sealed class CommandsHandlePendingCommandResult
{
@@ -4223,6 +4466,195 @@ public override void Write(Utf8JsonWriter writer, ExtensionStatus value, JsonSer
}
+/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion).
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct SlashCommandInputCompletion : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public SlashCommandInputCompletion(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the directory value.
+ public static SlashCommandInputCompletion Directory { get; } = new("directory");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(SlashCommandInputCompletion left, SlashCommandInputCompletion right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is SlashCommandInputCompletion other && Equals(other);
+
+ ///
+ public bool Equals(SlashCommandInputCompletion other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override SlashCommandInputCompletion Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, SlashCommandInputCompletion value, JsonSerializerOptions options)
+ {
+ GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandInputCompletion));
+ }
+ }
+}
+
+
+/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct SlashCommandKind : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public SlashCommandKind(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the builtin value.
+ public static SlashCommandKind Builtin { get; } = new("builtin");
+
+ /// Gets the skill value.
+ public static SlashCommandKind Skill { get; } = new("skill");
+
+ /// Gets the client value.
+ public static SlashCommandKind Client { get; } = new("client");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(SlashCommandKind left, SlashCommandKind right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(SlashCommandKind left, SlashCommandKind right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is SlashCommandKind other && Equals(other);
+
+ ///
+ public bool Equals(SlashCommandKind other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override SlashCommandKind Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, SlashCommandKind value, JsonSerializerOptions options)
+ {
+ GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandKind));
+ }
+ }
+}
+
+
+/// Optional target session mode.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct SlashCommandAgentPromptMode : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public SlashCommandAgentPromptMode(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the interactive value.
+ public static SlashCommandAgentPromptMode Interactive { get; } = new("interactive");
+
+ /// Gets the plan value.
+ public static SlashCommandAgentPromptMode Plan { get; } = new("plan");
+
+ /// Gets the autopilot value.
+ public static SlashCommandAgentPromptMode Autopilot { get; } = new("autopilot");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(SlashCommandAgentPromptMode left, SlashCommandAgentPromptMode right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(SlashCommandAgentPromptMode left, SlashCommandAgentPromptMode right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is SlashCommandAgentPromptMode other && Equals(other);
+
+ ///
+ public bool Equals(SlashCommandAgentPromptMode other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override SlashCommandAgentPromptMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, SlashCommandAgentPromptMode value, JsonSerializerOptions options)
+ {
+ GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(SlashCommandAgentPromptMode));
+ }
+ }
+}
+
+
/// The user's response: accept (submitted), decline (rejected), or cancel (dismissed).
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
@@ -4728,9 +5160,9 @@ internal ServerSessionsApi(JsonRpc rpc)
}
/// Calls "sessions.fork".
- public async Task ForkAsync(string sessionId, string? toEventId = null, CancellationToken cancellationToken = default)
+ public async Task ForkAsync(string sessionId, string? toEventId = null, string? name = null, CancellationToken cancellationToken = default)
{
- var request = new SessionsForkRequest { SessionId = sessionId, ToEventId = toEventId };
+ var request = new SessionsForkRequest { SessionId = sessionId, ToEventId = toEventId, Name = name };
return await CopilotClient.InvokeRpcAsync(_rpc, "sessions.fork", [request], cancellationToken);
}
}
@@ -5207,10 +5639,10 @@ public async Task DisableAsync(string name, CancellationToken cancellationToken
}
/// Calls "session.skills.reload".
- public async Task ReloadAsync(CancellationToken cancellationToken = default)
+ public async Task ReloadAsync(CancellationToken cancellationToken = default)
{
var request = new SessionSkillsReloadRequest { SessionId = _sessionId };
- await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.reload", [request], cancellationToken);
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.skills.reload", [request], cancellationToken);
}
}
@@ -5376,6 +5808,20 @@ internal CommandsApi(JsonRpc rpc, string sessionId)
_sessionId = sessionId;
}
+ /// Calls "session.commands.list".
+ public async Task ListAsync(CommandsListRequest? request = null, CancellationToken cancellationToken = default)
+ {
+ var rpcRequest = new CommandsListRequestWithSession { SessionId = _sessionId, IncludeBuiltins = request?.IncludeBuiltins, IncludeSkills = request?.IncludeSkills, IncludeClientCommands = request?.IncludeClientCommands };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.commands.list", [rpcRequest], cancellationToken);
+ }
+
+ /// Calls "session.commands.invoke".
+ public async Task InvokeAsync(string name, string? input = null, CancellationToken cancellationToken = default)
+ {
+ var request = new CommandsInvokeRequest { SessionId = _sessionId, Name = name, Input = input };
+ return await CopilotClient.InvokeRpcAsync(_rpc, "session.commands.invoke", [request], cancellationToken);
+ }
+
/// Calls "session.commands.handlePendingCommand".
public async Task HandlePendingCommandAsync(string requestId, string? error = null, CancellationToken cancellationToken = default)
{
@@ -5679,8 +6125,12 @@ public static void RegisterClientSessionApiHandlers(JsonRpc rpc, FuncTrue when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("isAutopilotContinuation")]
+ public bool? IsAutopilotContinuation { get; set; }
+
/// Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("nativeDocumentPathFallbackPaths")]
@@ -2049,6 +2054,11 @@ public partial class AssistantUsageData
[JsonPropertyName("apiCallId")]
public string? ApiCallId { get; set; }
+ /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary.
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ [JsonPropertyName("apiEndpoint")]
+ public AssistantUsageApiEndpoint? ApiEndpoint { get; set; }
+
/// Number of tokens read from prompt cache.
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("cacheReadTokens")]
@@ -5521,6 +5531,73 @@ public override void Write(Utf8JsonWriter writer, AssistantMessageToolRequestTyp
}
}
+/// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary.
+[JsonConverter(typeof(Converter))]
+[DebuggerDisplay("{Value,nq}")]
+public readonly struct AssistantUsageApiEndpoint : IEquatable
+{
+ private readonly string? _value;
+
+ /// Initializes a new instance of the struct.
+ /// The value to associate with this .
+ [JsonConstructor]
+ public AssistantUsageApiEndpoint(string value)
+ {
+ ArgumentException.ThrowIfNullOrWhiteSpace(value);
+ _value = value;
+ }
+
+ /// Gets the value associated with this .
+ public string Value => _value ?? string.Empty;
+
+ /// Gets the /chat/completions value.
+ public static AssistantUsageApiEndpoint ChatCompletions { get; } = new("/chat/completions");
+
+ /// Gets the /v1/messages value.
+ public static AssistantUsageApiEndpoint V1Messages { get; } = new("/v1/messages");
+
+ /// Gets the /responses value.
+ public static AssistantUsageApiEndpoint Responses { get; } = new("/responses");
+
+ /// Gets the ws:/responses value.
+ public static AssistantUsageApiEndpoint WsResponses { get; } = new("ws:/responses");
+
+ /// Returns a value indicating whether two instances are equivalent.
+ public static bool operator ==(AssistantUsageApiEndpoint left, AssistantUsageApiEndpoint right) => left.Equals(right);
+
+ /// Returns a value indicating whether two instances are not equivalent.
+ public static bool operator !=(AssistantUsageApiEndpoint left, AssistantUsageApiEndpoint right) => !(left == right);
+
+ ///
+ public override bool Equals(object? obj) => obj is AssistantUsageApiEndpoint other && Equals(other);
+
+ ///
+ public bool Equals(AssistantUsageApiEndpoint other) => string.Equals(Value, other.Value, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(Value);
+
+ ///
+ public override string ToString() => Value;
+
+ /// Provides a for serializing instances.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public sealed class Converter : JsonConverter
+ {
+ ///
+ public override AssistantUsageApiEndpoint Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
+ {
+ return new(GitHub.Copilot.SDK.GeneratedStringEnumJson.ReadValue(ref reader, typeToConvert));
+ }
+
+ ///
+ public override void Write(Utf8JsonWriter writer, AssistantUsageApiEndpoint value, JsonSerializerOptions options)
+ {
+ GitHub.Copilot.SDK.GeneratedStringEnumJson.WriteValue(writer, value.Value, typeof(AssistantUsageApiEndpoint));
+ }
+ }
+}
+
/// Where the failed model call originated.
[JsonConverter(typeof(Converter))]
[DebuggerDisplay("{Value,nq}")]
diff --git a/dotnet/test/Unit/SerializationTests.cs b/dotnet/test/Unit/SerializationTests.cs
index fa579ac6b..fd1d0228c 100644
--- a/dotnet/test/Unit/SerializationTests.cs
+++ b/dotnet/test/Unit/SerializationTests.cs
@@ -264,6 +264,21 @@ public void QueuedCommandResult_SerializesHandledAsBoolean_WithSdkOptions()
Assert.Null(deserialized.StopProcessingQueue);
}
+ [Fact]
+ public void PermissionDecision_SerializesBaseDiscriminator_WithSdkOptions()
+ {
+ var options = GetSerializerOptions();
+ var original = new PermissionDecision
+ {
+ Kind = PermissionRequestResultKind.Approved.Value
+ };
+
+ var json = JsonSerializer.Serialize(original, options);
+ using var document = JsonDocument.Parse(json);
+
+ Assert.Equal("approve-once", document.RootElement.GetProperty("kind").GetString());
+ }
+
private static JsonSerializerOptions GetSerializerOptions()
{
var prop = typeof(CopilotClient)
diff --git a/go/rpc/generated_rpc_union_test.go b/go/rpc/generated_rpc_union_test.go
index e2ca093df..a8a34ec60 100644
--- a/go/rpc/generated_rpc_union_test.go
+++ b/go/rpc/generated_rpc_union_test.go
@@ -2,7 +2,10 @@ package rpc
import (
"encoding/json"
+ "io"
"testing"
+
+ "github.com/github/copilot-sdk/go/internal/jsonrpc2"
)
func TestExternalToolResultJSONUnion(t *testing.T) {
@@ -130,6 +133,58 @@ func TestMcpServerConfigJSONUnion(t *testing.T) {
}
}
+func TestCommandsInvokeUnmarshalsSlashCommandInvocationResult(t *testing.T) {
+ clientToServerReader, clientToServerWriter := io.Pipe()
+ serverToClientReader, serverToClientWriter := io.Pipe()
+
+ client := jsonrpc2.NewClient(clientToServerWriter, serverToClientReader)
+ server := jsonrpc2.NewClient(serverToClientWriter, clientToServerReader)
+ server.SetRequestHandler("session.commands.invoke", func(params json.RawMessage) (json.RawMessage, *jsonrpc2.Error) {
+ var request struct {
+ Input string `json:"input"`
+ Name string `json:"name"`
+ SessionID string `json:"sessionId"`
+ }
+ if err := json.Unmarshal(params, &request); err != nil {
+ return nil, &jsonrpc2.Error{Code: -32602, Message: err.Error()}
+ }
+ if request.SessionID != "session-1" || request.Name != "help" || request.Input != "details" {
+ return nil, &jsonrpc2.Error{Code: -32602, Message: "unexpected invoke request"}
+ }
+ return json.RawMessage(`{"kind":"text","text":"hello","markdown":true}`), nil
+ })
+
+ client.Start()
+ server.Start()
+ t.Cleanup(func() {
+ client.Stop()
+ server.Stop()
+ _ = clientToServerWriter.Close()
+ _ = clientToServerReader.Close()
+ _ = serverToClientWriter.Close()
+ _ = serverToClientReader.Close()
+ })
+
+ input := "details"
+ result, err := NewSessionRpc(client, "session-1").Commands.Invoke(t.Context(), &CommandsInvokeRequest{
+ Input: &input,
+ Name: "help",
+ })
+ if err != nil {
+ t.Fatalf("invoke command: %v", err)
+ }
+ textResult, ok := result.(*SlashCommandTextResult)
+ if !ok {
+ t.Fatalf("invoke result = %T, want *SlashCommandTextResult", result)
+ }
+ if textResult.Text != "hello" {
+ t.Fatalf("invoke result text = %q, want hello", textResult.Text)
+ }
+ if textResult.Markdown == nil || !*textResult.Markdown {
+ t.Fatalf("invoke result markdown = %v, want true", textResult.Markdown)
+ }
+}
+
func TestUIElicitationFieldValueJSONUnion(t *testing.T) {
raw, err := json.Marshal(UIElicitationBooleanValue(true))
if err != nil {
diff --git a/go/rpc/zrpc.go b/go/rpc/zrpc.go
index 81e84d3c0..f83d26baa 100644
--- a/go/rpc/zrpc.go
+++ b/go/rpc/zrpc.go
@@ -93,6 +93,11 @@ type AgentSelectResult struct {
Agent AgentInfo `json:"agent"`
}
+type CommandList struct {
+ // Commands available in this session
+ Commands []SlashCommandInfo `json:"commands"`
+}
+
type CommandsHandlePendingCommandRequest struct {
// Error message if the command handler failed
Error *string `json:"error,omitempty"`
@@ -105,6 +110,22 @@ type CommandsHandlePendingCommandResult struct {
Success bool `json:"success"`
}
+type CommandsInvokeRequest struct {
+ // Raw input after the command name
+ Input *string `json:"input,omitempty"`
+ // Command name. Leading slashes are stripped and the name is matched case-insensitively.
+ Name string `json:"name"`
+}
+
+type CommandsListRequest struct {
+ // Include runtime built-in commands
+ IncludeBuiltins *bool `json:"includeBuiltins,omitempty"`
+ // Include commands registered by protocol clients, including SDK clients and extensions
+ IncludeClientCommands *bool `json:"includeClientCommands,omitempty"`
+ // Include enabled user-invocable skills and commands
+ IncludeSkills *bool `json:"includeSkills,omitempty"`
+}
+
type CommandsRespondToQueuedCommandRequest struct {
// Request ID from the queued command event
RequestID string `json:"requestId"`
@@ -705,6 +726,23 @@ type Model struct {
type ModelBilling struct {
// Billing cost multiplier relative to the base rate
Multiplier *float64 `json:"multiplier,omitempty"`
+ // Token-level pricing information for this model
+ TokenPrices *ModelBillingTokenPrices `json:"tokenPrices,omitempty"`
+}
+
+// Token-level pricing information for this model
+type ModelBillingTokenPrices struct {
+ // Number of tokens per standard billing batch
+ BatchSize *int64 `json:"batchSize,omitempty"`
+ // Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1
+ // AIU = $0.01 USD)
+ CachePrice *int64 `json:"cachePrice,omitempty"`
+ // Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU
+ // = $0.01 USD)
+ InputPrice *int64 `json:"inputPrice,omitempty"`
+ // Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1
+ // AIU = $0.01 USD)
+ OutputPrice *int64 `json:"outputPrice,omitempty"`
}
// Model capabilities and limits
@@ -1427,6 +1465,8 @@ type SessionFsWriteFileRequest struct {
// Experimental: SessionsForkRequest is part of an experimental API and may change or be
// removed.
type SessionsForkRequest struct {
+ // Optional friendly name to assign to the forked session.
+ Name *string `json:"name,omitempty"`
// Source session ID to fork from
SessionID string `json:"sessionId"`
// Optional event ID boundary. When provided, the fork includes only events before this ID
@@ -1437,6 +1477,8 @@ type SessionsForkRequest struct {
// Experimental: SessionsForkResult is part of an experimental API and may change or be
// removed.
type SessionsForkResult struct {
+ // Friendly name assigned to the forked session, if any.
+ Name *string `json:"name,omitempty"`
// The new forked session's ID
SessionID string `json:"sessionId"`
}
@@ -1527,9 +1569,107 @@ type SkillsEnableRequest struct {
type SkillsEnableResult struct {
}
-// Experimental: SkillsReloadResult is part of an experimental API and may change or be
+// Experimental: SkillsLoadDiagnostics is part of an experimental API and may change or be
// removed.
-type SkillsReloadResult struct {
+type SkillsLoadDiagnostics struct {
+ // Errors emitted while loading skills (e.g. skills that failed to load entirely)
+ Errors []string `json:"errors"`
+ // Warnings emitted while loading skills (e.g. skills that loaded but had issues)
+ Warnings []string `json:"warnings"`
+}
+
+type SlashCommandInfo struct {
+ // Canonical aliases without leading slashes
+ Aliases []string `json:"aliases,omitempty"`
+ // Whether the command may run while an agent turn is active
+ AllowDuringAgentExecution bool `json:"allowDuringAgentExecution"`
+ // Human-readable command description
+ Description string `json:"description"`
+ // Whether the command is experimental
+ Experimental *bool `json:"experimental,omitempty"`
+ // Optional unstructured input hint
+ Input *SlashCommandInput `json:"input,omitempty"`
+ // Coarse command category for grouping and behavior: runtime built-in, skill-backed
+ // command, or SDK/client-owned command
+ Kind SlashCommandKind `json:"kind"`
+ // Canonical command name without a leading slash
+ Name string `json:"name"`
+}
+
+// Optional unstructured input hint
+type SlashCommandInput struct {
+ // Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
+ Completion *SlashCommandInputCompletion `json:"completion,omitempty"`
+ // Hint to display when command input has not been provided
+ Hint string `json:"hint"`
+ // When true, clients should pass the full text after the command name as a single argument
+ // rather than splitting on whitespace
+ PreserveMultilineInput *bool `json:"preserveMultilineInput,omitempty"`
+ // When true, the command requires non-empty input; clients should render the input hint as
+ // required
+ Required *bool `json:"required,omitempty"`
+}
+
+type SlashCommandInvocationResult interface {
+ slashCommandInvocationResult()
+ Kind() SlashCommandInvocationResultKind
+}
+
+type RawSlashCommandInvocationResultData struct {
+ Discriminator SlashCommandInvocationResultKind
+ Raw json.RawMessage
+}
+
+func (RawSlashCommandInvocationResultData) slashCommandInvocationResult() {}
+func (r RawSlashCommandInvocationResultData) Kind() SlashCommandInvocationResultKind {
+ return r.Discriminator
+}
+
+type SlashCommandAgentPromptResult struct {
+ // Prompt text to display to the user
+ DisplayPrompt string `json:"displayPrompt"`
+ // Optional target session mode
+ Mode *SlashCommandAgentPromptMode `json:"mode,omitempty"`
+ // Prompt to submit to the agent
+ Prompt string `json:"prompt"`
+ // True when the invocation mutated user runtime settings; consumers caching settings should
+ // refresh
+ RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"`
+}
+
+func (SlashCommandAgentPromptResult) slashCommandInvocationResult() {}
+func (SlashCommandAgentPromptResult) Kind() SlashCommandInvocationResultKind {
+ return SlashCommandInvocationResultKindAgentPrompt
+}
+
+type SlashCommandCompletedResult struct {
+ // Optional user-facing message describing the completed command
+ Message *string `json:"message,omitempty"`
+ // True when the invocation mutated user runtime settings; consumers caching settings should
+ // refresh
+ RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"`
+}
+
+func (SlashCommandCompletedResult) slashCommandInvocationResult() {}
+func (SlashCommandCompletedResult) Kind() SlashCommandInvocationResultKind {
+ return SlashCommandInvocationResultKindCompleted
+}
+
+type SlashCommandTextResult struct {
+ // Whether text contains Markdown
+ Markdown *bool `json:"markdown,omitempty"`
+ // Whether ANSI sequences should be preserved
+ PreserveAnsi *bool `json:"preserveAnsi,omitempty"`
+ // True when the invocation mutated user runtime settings; consumers caching settings should
+ // refresh
+ RuntimeSettingsChanged *bool `json:"runtimeSettingsChanged,omitempty"`
+ // Text output for the client to render
+ Text string `json:"text"`
+}
+
+func (SlashCommandTextResult) slashCommandInvocationResult() {}
+func (SlashCommandTextResult) Kind() SlashCommandInvocationResultKind {
+ return SlashCommandInvocationResultKindText
}
type SuspendResult struct {
@@ -2326,6 +2466,41 @@ const (
ShellKillSignalSIGTERM ShellKillSignal = "SIGTERM"
)
+// Optional target session mode
+type SlashCommandAgentPromptMode string
+
+const (
+ SlashCommandAgentPromptModeAutopilot SlashCommandAgentPromptMode = "autopilot"
+ SlashCommandAgentPromptModeInteractive SlashCommandAgentPromptMode = "interactive"
+ SlashCommandAgentPromptModePlan SlashCommandAgentPromptMode = "plan"
+)
+
+// Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
+type SlashCommandInputCompletion string
+
+const (
+ SlashCommandInputCompletionDirectory SlashCommandInputCompletion = "directory"
+)
+
+// Kind discriminator for SlashCommandInvocationResult.
+type SlashCommandInvocationResultKind string
+
+const (
+ SlashCommandInvocationResultKindAgentPrompt SlashCommandInvocationResultKind = "agent-prompt"
+ SlashCommandInvocationResultKindCompleted SlashCommandInvocationResultKind = "completed"
+ SlashCommandInvocationResultKindText SlashCommandInvocationResultKind = "text"
+)
+
+// Coarse command category for grouping and behavior: runtime built-in, skill-backed
+// command, or SDK/client-owned command
+type SlashCommandKind string
+
+const (
+ SlashCommandKindBuiltin SlashCommandKind = "builtin"
+ SlashCommandKindClient SlashCommandKind = "client"
+ SlashCommandKindSkill SlashCommandKind = "skill"
+)
+
// How the agent is currently being managed by the runtime
type TaskAgentInfoExecutionMode string
@@ -2443,8 +2618,12 @@ type serverApi struct {
type ServerAccountApi serverApi
-func (a *ServerAccountApi) GetQuota(ctx context.Context, params *AccountGetQuotaRequest) (*AccountGetQuotaResult, error) {
- raw, err := a.client.Request("account.getQuota", params)
+func (a *ServerAccountApi) GetQuota(ctx context.Context, params ...*AccountGetQuotaRequest) (*AccountGetQuotaResult, error) {
+ var requestParams *AccountGetQuotaRequest
+ if len(params) > 0 {
+ requestParams = params[0]
+ }
+ raw, err := a.client.Request("account.getQuota", requestParams)
if err != nil {
return nil, err
}
@@ -2549,8 +2728,12 @@ func (s *ServerMcpApi) Config() *ServerMcpConfigApi {
type ServerModelsApi serverApi
-func (a *ServerModelsApi) List(ctx context.Context, params *ModelsListRequest) (*ModelList, error) {
- raw, err := a.client.Request("models.list", params)
+func (a *ServerModelsApi) List(ctx context.Context, params ...*ModelsListRequest) (*ModelList, error) {
+ var requestParams *ModelsListRequest
+ if len(params) > 0 {
+ requestParams = params[0]
+ }
+ raw, err := a.client.Request("models.list", requestParams)
if err != nil {
return nil, err
}
@@ -2818,6 +3001,53 @@ func (a *CommandsApi) HandlePendingCommand(ctx context.Context, params *Commands
return &result, nil
}
+func (a *CommandsApi) Invoke(ctx context.Context, params *CommandsInvokeRequest) (SlashCommandInvocationResult, error) {
+ req := map[string]any{"sessionId": a.sessionID}
+ if params != nil {
+ if params.Input != nil {
+ req["input"] = *params.Input
+ }
+ req["name"] = params.Name
+ }
+ raw, err := a.client.Request("session.commands.invoke", req)
+ if err != nil {
+ return nil, err
+ }
+ result, err := unmarshalSlashCommandInvocationResult(raw)
+ if err != nil {
+ return nil, err
+ }
+ return result, nil
+}
+
+func (a *CommandsApi) List(ctx context.Context, params ...*CommandsListRequest) (*CommandList, error) {
+ var requestParams *CommandsListRequest
+ if len(params) > 0 {
+ requestParams = params[0]
+ }
+ req := map[string]any{"sessionId": a.sessionID}
+ if requestParams != nil {
+ if requestParams.IncludeBuiltins != nil {
+ req["includeBuiltins"] = *requestParams.IncludeBuiltins
+ }
+ if requestParams.IncludeClientCommands != nil {
+ req["includeClientCommands"] = *requestParams.IncludeClientCommands
+ }
+ if requestParams.IncludeSkills != nil {
+ req["includeSkills"] = *requestParams.IncludeSkills
+ }
+ }
+ raw, err := a.client.Request("session.commands.list", req)
+ if err != nil {
+ return nil, err
+ }
+ var result CommandList
+ if err := json.Unmarshal(raw, &result); err != nil {
+ return nil, err
+ }
+ return &result, nil
+}
+
func (a *CommandsApi) RespondToQueuedCommand(ctx context.Context, params *CommandsRespondToQueuedCommandRequest) (*CommandsRespondToQueuedCommandResult, error) {
req := map[string]any{"sessionId": a.sessionID}
if params != nil {
@@ -3385,13 +3615,13 @@ func (a *SkillsApi) List(ctx context.Context) (*SkillList, error) {
return &result, nil
}
-func (a *SkillsApi) Reload(ctx context.Context) (*SkillsReloadResult, error) {
+func (a *SkillsApi) Reload(ctx context.Context) (*SkillsLoadDiagnostics, error) {
req := map[string]any{"sessionId": a.sessionID}
raw, err := a.client.Request("session.skills.reload", req)
if err != nil {
return nil, err
}
- var result SkillsReloadResult
+ var result SkillsLoadDiagnostics
if err := json.Unmarshal(raw, &result); err != nil {
return nil, err
}
diff --git a/go/rpc/zrpc_encoding.go b/go/rpc/zrpc_encoding.go
index b4ab8518c..bf77c3c2e 100644
--- a/go/rpc/zrpc_encoding.go
+++ b/go/rpc/zrpc_encoding.go
@@ -1054,6 +1054,86 @@ func (r *PermissionDecisionRequest) UnmarshalJSON(data []byte) error {
return nil
}
+func unmarshalSlashCommandInvocationResult(data []byte) (SlashCommandInvocationResult, error) {
+ if string(data) == "null" {
+ return nil, nil
+ }
+ type rawUnion struct {
+ Kind SlashCommandInvocationResultKind `json:"kind"`
+ }
+ var raw rawUnion
+ if err := json.Unmarshal(data, &raw); err != nil {
+ return nil, err
+ }
+
+ switch raw.Kind {
+ case SlashCommandInvocationResultKindAgentPrompt:
+ var d SlashCommandAgentPromptResult
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ case SlashCommandInvocationResultKindCompleted:
+ var d SlashCommandCompletedResult
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ case SlashCommandInvocationResultKindText:
+ var d SlashCommandTextResult
+ if err := json.Unmarshal(data, &d); err != nil {
+ return nil, err
+ }
+ return &d, nil
+ default:
+ return &RawSlashCommandInvocationResultData{Discriminator: raw.Kind, Raw: data}, nil
+ }
+}
+
+func (r RawSlashCommandInvocationResultData) MarshalJSON() ([]byte, error) {
+ if r.Raw != nil {
+ return r.Raw, nil
+ }
+ return json.Marshal(struct {
+ Kind SlashCommandInvocationResultKind `json:"kind"`
+ }{
+ Kind: r.Discriminator,
+ })
+}
+
+func (r SlashCommandAgentPromptResult) MarshalJSON() ([]byte, error) {
+ type alias SlashCommandAgentPromptResult
+ return json.Marshal(struct {
+ Kind SlashCommandInvocationResultKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
+func (r SlashCommandCompletedResult) MarshalJSON() ([]byte, error) {
+ type alias SlashCommandCompletedResult
+ return json.Marshal(struct {
+ Kind SlashCommandInvocationResultKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
+func (r SlashCommandTextResult) MarshalJSON() ([]byte, error) {
+ type alias SlashCommandTextResult
+ return json.Marshal(struct {
+ Kind SlashCommandInvocationResultKind `json:"kind"`
+ alias
+ }{
+ Kind: r.Kind(),
+ alias: alias(r),
+ })
+}
+
func unmarshalTaskInfo(data []byte) (TaskInfo, error) {
if string(data) == "null" {
return nil, nil
diff --git a/go/zsession_encoding.go b/go/zsession_encoding.go
index 72fa12aed..fc603c5ca 100644
--- a/go/zsession_encoding.go
+++ b/go/zsession_encoding.go
@@ -670,6 +670,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error {
Attachments []json.RawMessage `json:"attachments,omitempty"`
Content string `json:"content"`
InteractionID *string `json:"interactionId,omitempty"`
+ IsAutopilotContinuation *bool `json:"isAutopilotContinuation,omitempty"`
NativeDocumentPathFallbackPaths []string `json:"nativeDocumentPathFallbackPaths,omitempty"`
ParentAgentTaskID *string `json:"parentAgentTaskId,omitempty"`
Source *string `json:"source,omitempty"`
@@ -693,6 +694,7 @@ func (r *UserMessageData) UnmarshalJSON(data []byte) error {
}
r.Content = raw.Content
r.InteractionID = raw.InteractionID
+ r.IsAutopilotContinuation = raw.IsAutopilotContinuation
r.NativeDocumentPathFallbackPaths = raw.NativeDocumentPathFallbackPaths
r.ParentAgentTaskID = raw.ParentAgentTaskID
r.Source = raw.Source
diff --git a/go/zsession_events.go b/go/zsession_events.go
index 0c62df5c4..c92c53d7e 100644
--- a/go/zsession_events.go
+++ b/go/zsession_events.go
@@ -522,6 +522,8 @@ func (*SessionInfoData) Type() SessionEventType { return SessionEventTypeSession
type AssistantUsageData struct {
// Completion ID from the model provider (e.g., chatcmpl-abc123)
APICallID *string `json:"apiCallId,omitempty"`
+ // API endpoint used for this model call, matching CAPI supported_endpoints vocabulary
+ APIEndpoint *AssistantUsageAPIEndpoint `json:"apiEndpoint,omitempty"`
// Number of tokens read from prompt cache
CacheReadTokens *float64 `json:"cacheReadTokens,omitempty"`
// Number of tokens written to prompt cache
@@ -1337,6 +1339,8 @@ type UserMessageData struct {
Content string `json:"content"`
// CAPI interaction ID for correlating this user message with its turn
InteractionID *string `json:"interactionId,omitempty"`
+ // True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry.
+ IsAutopilotContinuation *bool `json:"isAutopilotContinuation,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
@@ -2753,6 +2757,16 @@ const (
AssistantMessageToolRequestTypeFunction AssistantMessageToolRequestType = "function"
)
+// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary
+type AssistantUsageAPIEndpoint string
+
+const (
+ AssistantUsageAPIEndpointChatCompletions AssistantUsageAPIEndpoint = "/chat/completions"
+ AssistantUsageAPIEndpointResponses AssistantUsageAPIEndpoint = "/responses"
+ AssistantUsageAPIEndpointV1Messages AssistantUsageAPIEndpoint = "/v1/messages"
+ AssistantUsageAPIEndpointWsResponses AssistantUsageAPIEndpoint = "ws:/responses"
+)
+
// The user action: "accept" (submitted form), "decline" (explicitly refused), or "cancel" (dismissed)
type ElicitationCompletedAction string
diff --git a/nodejs/package-lock.json b/nodejs/package-lock.json
index 97dbbd3d7..b341f485c 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.46",
+ "@github/copilot": "^1.0.47",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
@@ -663,26 +663,26 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.46.tgz",
- "integrity": "sha512-e3gxCj8DLGesTAZQ5+jCCbCxe3lMyjKfs5eLgER/SID8Rcb7YpgBXoUvOn3eXxLSsJEmJ3GagHaaHDkf3Zm+Ng==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.47.tgz",
+ "integrity": "sha512-U4WrajOOjjMVleqIRvRt+kDsjYQPLHxtJMMtdzW2N18dbRddlxqN+qo6ZOxOTy3tks2+YI+G89zyO1qpxpuWSg==",
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.46",
- "@github/copilot-darwin-x64": "1.0.46",
- "@github/copilot-linux-arm64": "1.0.46",
- "@github/copilot-linux-x64": "1.0.46",
- "@github/copilot-win32-arm64": "1.0.46",
- "@github/copilot-win32-x64": "1.0.46"
+ "@github/copilot-darwin-arm64": "1.0.47",
+ "@github/copilot-darwin-x64": "1.0.47",
+ "@github/copilot-linux-arm64": "1.0.47",
+ "@github/copilot-linux-x64": "1.0.47",
+ "@github/copilot-win32-arm64": "1.0.47",
+ "@github/copilot-win32-x64": "1.0.47"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.46.tgz",
- "integrity": "sha512-zbhXuRguCdDgeIZKH+rjgBM/6CDMUmhLMck8w9XFDxUY2wrP7MSWXuX8yA4/1H3ySOTZMIH1G5DQpWh+npmR2Q==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.47.tgz",
+ "integrity": "sha512-sGuN+7VfBjOTbPkyKFm0dPfp1hwyNsJVkNsV+3xmOwVsGy3nhROc76sQ5SWWSmyDGl7H58KnpPazlSDwbpf4PQ==",
"cpu": [
"arm64"
],
@@ -696,9 +696,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.46.tgz",
- "integrity": "sha512-kSUcV6cARhM+b/BuNSQtazbORTetRjIWpO3SqWSmH+2UoeZP5A5x+ipr7mhshq+E+pcWPeQKMGbKGY3lrCSMFw==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.47.tgz",
+ "integrity": "sha512-nVHYbzvOau5zy4nONWZPXROIrqzd7DhY12bMkE7spLe7lj0Sh6MFtTdPpMT7kkaObEikGYLTrZtOUpguwqHkmA==",
"cpu": [
"x64"
],
@@ -712,9 +712,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.46.tgz",
- "integrity": "sha512-Tz3F0LuGFbOvvv0VKQJ4E5XYBsTdqTNMAwOhbkwX6TuKMX88uLJNKP5uPf6yuu1z3J3nt/5rfEd9CxVrZbnqLA==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.47.tgz",
+ "integrity": "sha512-7aDoE6pnSGcCTuPdJKyHfzif/Rj1z5UE0gLMHHQMo1QIYJkUZFX7mV8Ng4zB+2edq8lNL5DiYRcbFajV54ibSg==",
"cpu": [
"arm64"
],
@@ -728,9 +728,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.46.tgz",
- "integrity": "sha512-s9JWe/YE78I7QEeXrvDGHB5x2XnnkegUJYVE9QR2DI/qLXviHMarM3akOUhed21uVqzoiLPacXKZcTcaDO8tOg==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.47.tgz",
+ "integrity": "sha512-wB5ekOdoxM/6Ogguk54fqJTHTRJkXwUIyzrbYaMy7zANE82jeRE1PQqs+5SdUZXq2IBMZIN1vq6bM56gpb54qg==",
"cpu": [
"x64"
],
@@ -744,9 +744,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.46.tgz",
- "integrity": "sha512-auX8o8vG8A+rdSthvey1D8q3o6lNlNIfHFjoBU0Z9Fxid6Ghz2paaAn0/Uwz9Ev8W8cn/5C5kEPs3niMXSh4Jw==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.47.tgz",
+ "integrity": "sha512-AenPXpTeXApOh25biS+Vmc1Uau78OLHxeXjXDF6Po07xWO7fVzorEK0hnSoD6xmpjptvP2MDSMk4as7jyvM0sQ==",
"cpu": [
"arm64"
],
@@ -760,9 +760,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.46.tgz",
- "integrity": "sha512-iXo9TUqtSxqlBfC+SZSQMrctKJpWR19zr+8dk7hczE42gOVB0/A+NySJwCmY3UFAEY98lbLDjIC+NCbYFcpEHA==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.47.tgz",
+ "integrity": "sha512-35bOBTTIm31rgbvFDogAMojWMSV6sLTd3mGjLl1Lf/d0KZGCGLqWXAYMAcV3grEjiAEXxlLLzNs8OfBR/9OdZg==",
"cpu": [
"x64"
],
diff --git a/nodejs/package.json b/nodejs/package.json
index 60002a2aa..7067cc76c 100644
--- a/nodejs/package.json
+++ b/nodejs/package.json
@@ -56,7 +56,7 @@
"author": "GitHub",
"license": "MIT",
"dependencies": {
- "@github/copilot": "^1.0.46",
+ "@github/copilot": "^1.0.47",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/samples/package-lock.json b/nodejs/samples/package-lock.json
index f2208347e..061d3f3b1 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.46",
+ "@github/copilot": "^1.0.47",
"vscode-jsonrpc": "^8.2.1",
"zod": "^4.3.6"
},
diff --git a/nodejs/src/generated/rpc.ts b/nodejs/src/generated/rpc.ts
index 57aeb6f69..36dcb5952 100644
--- a/nodejs/src/generated/rpc.ts
+++ b/nodejs/src/generated/rpc.ts
@@ -12,6 +12,20 @@ import type { MessageConnection } from "vscode-jsonrpc/node.js";
* via the `definition` "AuthInfoType".
*/
export type AuthInfoType = "hmac" | "env" | "user" | "gh-cli" | "api-key" | "token" | "copilot-api-token";
+/**
+ * Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "SlashCommandKind".
+ */
+export type SlashCommandKind = "builtin" | "skill" | "client";
+/**
+ * Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "SlashCommandInputCompletion".
+ */
+export type SlashCommandInputCompletion = "directory";
/**
* Result of the queued command execution
*
@@ -235,6 +249,18 @@ export type SessionFsSetProviderConventions = "windows" | "posix";
* via the `definition` "ShellKillSignal".
*/
export type ShellKillSignal = "SIGTERM" | "SIGKILL" | "SIGINT";
+/**
+ * Optional target session mode
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "SlashCommandAgentPromptMode".
+ */
+export type SlashCommandAgentPromptMode = "interactive" | "plan" | "autopilot";
+
+export type SlashCommandInvocationResult =
+ | SlashCommandTextResult
+ | SlashCommandAgentPromptResult
+ | SlashCommandCompletedResult;
/**
* Current lifecycle status of the task
*
@@ -402,6 +428,59 @@ export interface AgentSelectResult {
agent: AgentInfo;
}
+export interface CommandList {
+ /**
+ * Commands available in this session
+ */
+ commands: SlashCommandInfo[];
+}
+
+export interface SlashCommandInfo {
+ /**
+ * Canonical command name without a leading slash
+ */
+ name: string;
+ /**
+ * Canonical aliases without leading slashes
+ */
+ aliases?: string[];
+ /**
+ * Human-readable command description
+ */
+ description: string;
+ kind: SlashCommandKind;
+ input?: SlashCommandInput;
+ /**
+ * Whether the command may run while an agent turn is active
+ */
+ allowDuringAgentExecution: boolean;
+ /**
+ * Whether the command is experimental
+ */
+ experimental?: boolean;
+}
+/**
+ * Optional unstructured input hint
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "SlashCommandInput".
+ */
+export interface SlashCommandInput {
+ /**
+ * Hint to display when command input has not been provided
+ */
+ hint: string;
+ /**
+ * When true, the command requires non-empty input; clients should render the input hint as required
+ */
+ required?: boolean;
+ completion?: SlashCommandInputCompletion;
+ /**
+ * When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace
+ */
+ preserveMultilineInput?: boolean;
+}
+
export interface CommandsHandlePendingCommandRequest {
/**
* Request ID from the command invocation event
@@ -420,6 +499,32 @@ export interface CommandsHandlePendingCommandResult {
success: boolean;
}
+export interface CommandsInvokeRequest {
+ /**
+ * Command name. Leading slashes are stripped and the name is matched case-insensitively.
+ */
+ name: string;
+ /**
+ * Raw input after the command name
+ */
+ input?: string;
+}
+
+export interface CommandsListRequest {
+ /**
+ * Include runtime built-in commands
+ */
+ includeBuiltins?: boolean;
+ /**
+ * Include enabled user-invocable skills and commands
+ */
+ includeSkills?: boolean;
+ /**
+ * Include commands registered by protocol clients, including SDK clients and extensions
+ */
+ includeClientCommands?: boolean;
+}
+
export interface CommandsRespondToQueuedCommandRequest {
/**
* Request ID from the queued command event
@@ -1199,6 +1304,31 @@ export interface ModelBilling {
* Billing cost multiplier relative to the base rate
*/
multiplier?: number;
+ tokenPrices?: ModelBillingTokenPrices;
+}
+/**
+ * Token-level pricing information for this model
+ *
+ * This interface was referenced by `_RpcSchemaRoot`'s JSON-Schema
+ * via the `definition` "ModelBillingTokenPrices".
+ */
+export interface ModelBillingTokenPrices {
+ /**
+ * Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ */
+ inputPrice?: number;
+ /**
+ * Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ */
+ outputPrice?: number;
+ /**
+ * Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ */
+ cachePrice?: number;
+ /**
+ * Number of tokens per standard billing batch
+ */
+ batchSize?: number;
}
/**
* Override individual model capabilities resolved by the runtime
@@ -1882,6 +2012,10 @@ export interface SessionsForkRequest {
* Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included.
*/
toEventId?: string;
+ /**
+ * Optional friendly name to assign to the forked session.
+ */
+ name?: string;
}
/** @experimental */
@@ -1890,6 +2024,10 @@ export interface SessionsForkResult {
* The new forked session's ID
*/
sessionId: string;
+ /**
+ * Friendly name assigned to the forked session, if any.
+ */
+ name?: string;
}
export interface ShellExecRequest {
@@ -1998,6 +2136,76 @@ export interface SkillsEnableRequest {
name: string;
}
+/** @experimental */
+export interface SkillsLoadDiagnostics {
+ /**
+ * Warnings emitted while loading skills (e.g. skills that loaded but had issues)
+ */
+ warnings: string[];
+ /**
+ * Errors emitted while loading skills (e.g. skills that failed to load entirely)
+ */
+ errors: string[];
+}
+
+export interface SlashCommandAgentPromptResult {
+ /**
+ * Agent prompt result discriminator
+ */
+ kind: "agent-prompt";
+ /**
+ * Prompt to submit to the agent
+ */
+ prompt: string;
+ /**
+ * Prompt text to display to the user
+ */
+ displayPrompt: string;
+ mode?: SlashCommandAgentPromptMode;
+ /**
+ * True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ */
+ runtimeSettingsChanged?: boolean;
+}
+
+export interface SlashCommandCompletedResult {
+ /**
+ * Completed result discriminator
+ */
+ kind: "completed";
+ /**
+ * Optional user-facing message describing the completed command
+ */
+ message?: string;
+ /**
+ * True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ */
+ runtimeSettingsChanged?: boolean;
+}
+
+export interface SlashCommandTextResult {
+ /**
+ * Text result discriminator
+ */
+ kind: "text";
+ /**
+ * Text output for the client to render
+ */
+ text: string;
+ /**
+ * Whether text contains Markdown
+ */
+ markdown?: boolean;
+ /**
+ * Whether ANSI sequences should be preserved
+ */
+ preserveAnsi?: boolean;
+ /**
+ * True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ */
+ runtimeSettingsChanged?: boolean;
+}
+
export interface TaskAgentInfo {
/**
* Task kind
@@ -2765,7 +2973,7 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
connection.sendRequest("session.skills.enable", { sessionId, ...params }),
disable: async (params: SkillsDisableRequest): Promise =>
connection.sendRequest("session.skills.disable", { sessionId, ...params }),
- reload: async (): Promise =>
+ reload: async (): Promise =>
connection.sendRequest("session.skills.reload", { sessionId }),
},
/** @experimental */
@@ -2805,6 +3013,10 @@ export function createSessionRpc(connection: MessageConnection, sessionId: strin
connection.sendRequest("session.tools.handlePendingToolCall", { sessionId, ...params }),
},
commands: {
+ list: async (params?: CommandsListRequest): Promise =>
+ connection.sendRequest("session.commands.list", { sessionId, ...params }),
+ invoke: async (params: CommandsInvokeRequest): Promise =>
+ connection.sendRequest("session.commands.invoke", { sessionId, ...params }),
handlePendingCommand: async (params: CommandsHandlePendingCommandRequest): Promise =>
connection.sendRequest("session.commands.handlePendingCommand", { sessionId, ...params }),
respondToQueuedCommand: async (params: CommandsRespondToQueuedCommandRequest): Promise =>
diff --git a/nodejs/src/generated/session-events.ts b/nodejs/src/generated/session-events.ts
index 6f2bab31c..606cffae9 100644
--- a/nodejs/src/generated/session-events.ts
+++ b/nodejs/src/generated/session-events.ts
@@ -125,6 +125,10 @@ export type UserMessageAttachmentGithubReferenceType = "issue" | "pr" | "discuss
* Tool call type: "function" for standard tool calls, "custom" for grammar-based tool calls. Defaults to "function" when absent.
*/
export type AssistantMessageToolRequestType = "function" | "custom";
+/**
+ * API endpoint used for this model call, matching CAPI supported_endpoints vocabulary
+ */
+export type AssistantUsageApiEndpoint = "/chat/completions" | "/v1/messages" | "/responses" | "ws:/responses";
/**
* Where the failed model call originated
*/
@@ -1582,6 +1586,10 @@ export interface UserMessageData {
* CAPI interaction ID for correlating this user message with its turn
*/
interactionId?: string;
+ /**
+ * True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry.
+ */
+ isAutopilotContinuation?: boolean;
/**
* Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit
*/
@@ -2207,6 +2215,7 @@ export interface AssistantUsageData {
* Completion ID from the model provider (e.g., chatcmpl-abc123)
*/
apiCallId?: string;
+ apiEndpoint?: AssistantUsageApiEndpoint;
/**
* Number of tokens read from prompt cache
*/
diff --git a/python/copilot/generated/rpc.py b/python/copilot/generated/rpc.py
index c4f6fd56b..9c7329be1 100644
--- a/python/copilot/generated/rpc.py
+++ b/python/copilot/generated/rpc.py
@@ -207,6 +207,19 @@ class AuthInfoType(Enum):
TOKEN = "token"
USER = "user"
+class SlashCommandInputCompletion(Enum):
+ """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)"""
+
+ DIRECTORY = "directory"
+
+class SlashCommandKind(Enum):
+ """Coarse command category for grouping and behavior: runtime built-in, skill-backed
+ command, or SDK/client-owned command
+ """
+ BUILTIN = "builtin"
+ CLIENT = "client"
+ SKILL = "skill"
+
@dataclass
class CommandsHandlePendingCommandRequest:
request_id: str
@@ -245,6 +258,57 @@ def to_dict(self) -> dict:
result["success"] = from_bool(self.success)
return result
+@dataclass
+class CommandsInvokeRequest:
+ name: str
+ """Command name. Leading slashes are stripped and the name is matched case-insensitively."""
+
+ input: str | None = None
+ """Raw input after the command name"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'CommandsInvokeRequest':
+ assert isinstance(obj, dict)
+ name = from_str(obj.get("name"))
+ input = from_union([from_str, from_none], obj.get("input"))
+ return CommandsInvokeRequest(name, input)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["name"] = from_str(self.name)
+ if self.input is not None:
+ result["input"] = from_union([from_str, from_none], self.input)
+ return result
+
+@dataclass
+class CommandsListRequest:
+ include_builtins: bool | None = None
+ """Include runtime built-in commands"""
+
+ include_client_commands: bool | None = None
+ """Include commands registered by protocol clients, including SDK clients and extensions"""
+
+ include_skills: bool | None = None
+ """Include enabled user-invocable skills and commands"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'CommandsListRequest':
+ assert isinstance(obj, dict)
+ include_builtins = from_union([from_bool, from_none], obj.get("includeBuiltins"))
+ include_client_commands = from_union([from_bool, from_none], obj.get("includeClientCommands"))
+ include_skills = from_union([from_bool, from_none], obj.get("includeSkills"))
+ return CommandsListRequest(include_builtins, include_client_commands, include_skills)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.include_builtins is not None:
+ result["includeBuiltins"] = from_union([from_bool, from_none], self.include_builtins)
+ if self.include_client_commands is not None:
+ result["includeClientCommands"] = from_union([from_bool, from_none], self.include_client_commands)
+ if self.include_skills is not None:
+ result["includeSkills"] = from_union([from_bool, from_none], self.include_skills)
+ return result
+
@dataclass
class QueuedCommandResult:
"""Result of the queued command execution"""
@@ -538,7 +602,7 @@ class ExternalToolTextResultForLlmContentResourceLinkType(Enum):
class ExternalToolTextResultForLlmContentTerminalType(Enum):
TERMINAL = "terminal"
-class ExternalToolTextResultForLlmContentTextType(Enum):
+class KindEnum(Enum):
TEXT = "text"
class FilterMappingString(Enum):
@@ -917,30 +981,54 @@ class MCPServerConfigLocalType(Enum):
LOCAL = "local"
STDIO = "stdio"
-class SessionMode(Enum):
- """The agent mode. Valid values: "interactive", "plan", "autopilot"."""
+class Mode(Enum):
+ """The agent mode. Valid values: "interactive", "plan", "autopilot".
+ Optional target session mode
+ """
AUTOPILOT = "autopilot"
INTERACTIVE = "interactive"
PLAN = "plan"
@dataclass
-class ModelBilling:
- """Billing information"""
+class ModelBillingTokenPrices:
+ """Token-level pricing information for this model"""
- multiplier: float | None = None
- """Billing cost multiplier relative to the base rate"""
+ batch_size: int | None = None
+ """Number of tokens per standard billing batch"""
+
+ cache_price: int | None = None
+ """Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1
+ AIU = $0.01 USD)
+ """
+ input_price: int | None = None
+ """Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU
+ = $0.01 USD)
+ """
+ output_price: int | None = None
+ """Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1
+ AIU = $0.01 USD)
+ """
@staticmethod
- def from_dict(obj: Any) -> 'ModelBilling':
+ def from_dict(obj: Any) -> 'ModelBillingTokenPrices':
assert isinstance(obj, dict)
- multiplier = from_union([from_float, from_none], obj.get("multiplier"))
- return ModelBilling(multiplier)
+ batch_size = from_union([from_int, from_none], obj.get("batchSize"))
+ cache_price = from_union([from_int, from_none], obj.get("cachePrice"))
+ input_price = from_union([from_int, from_none], obj.get("inputPrice"))
+ output_price = from_union([from_int, from_none], obj.get("outputPrice"))
+ return ModelBillingTokenPrices(batch_size, cache_price, input_price, output_price)
def to_dict(self) -> dict:
result: dict = {}
- if self.multiplier is not None:
- result["multiplier"] = from_union([to_float, from_none], self.multiplier)
+ if self.batch_size is not None:
+ result["batchSize"] = from_union([from_int, from_none], self.batch_size)
+ if self.cache_price is not None:
+ result["cachePrice"] = from_union([from_int, from_none], self.cache_price)
+ if self.input_price is not None:
+ result["inputPrice"] = from_union([from_int, from_none], self.input_price)
+ if self.output_price is not None:
+ result["outputPrice"] = from_union([from_int, from_none], self.output_price)
return result
@dataclass
@@ -1829,6 +1917,9 @@ class SessionsForkRequest:
session_id: str
"""Source session ID to fork from"""
+ name: str | None = None
+ """Optional friendly name to assign to the forked session."""
+
to_event_id: str | None = None
"""Optional event ID boundary. When provided, the fork includes only events before this ID
(exclusive). When omitted, all events are included.
@@ -1838,12 +1929,15 @@ class SessionsForkRequest:
def from_dict(obj: Any) -> 'SessionsForkRequest':
assert isinstance(obj, dict)
session_id = from_str(obj.get("sessionId"))
+ name = from_union([from_str, from_none], obj.get("name"))
to_event_id = from_union([from_str, from_none], obj.get("toEventId"))
- return SessionsForkRequest(session_id, to_event_id)
+ return SessionsForkRequest(session_id, name, to_event_id)
def to_dict(self) -> dict:
result: dict = {}
result["sessionId"] = from_str(self.session_id)
+ if self.name is not None:
+ result["name"] = from_union([from_str, from_none], self.name)
if self.to_event_id is not None:
result["toEventId"] = from_union([from_str, from_none], self.to_event_id)
return result
@@ -1854,15 +1948,21 @@ class SessionsForkResult:
session_id: str
"""The new forked session's ID"""
+ name: str | None = None
+ """Friendly name assigned to the forked session, if any."""
+
@staticmethod
def from_dict(obj: Any) -> 'SessionsForkResult':
assert isinstance(obj, dict)
session_id = from_str(obj.get("sessionId"))
- return SessionsForkResult(session_id)
+ name = from_union([from_str, from_none], obj.get("name"))
+ return SessionsForkResult(session_id, name)
def to_dict(self) -> dict:
result: dict = {}
result["sessionId"] = from_str(self.session_id)
+ if self.name is not None:
+ result["name"] = from_union([from_str, from_none], self.name)
return result
@dataclass
@@ -2047,6 +2147,39 @@ def to_dict(self) -> dict:
result["name"] = from_str(self.name)
return result
+# Experimental: this type is part of an experimental API and may change or be removed.
+@dataclass
+class SkillsLoadDiagnostics:
+ errors: list[str]
+ """Errors emitted while loading skills (e.g. skills that failed to load entirely)"""
+
+ warnings: list[str]
+ """Warnings emitted while loading skills (e.g. skills that loaded but had issues)"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SkillsLoadDiagnostics':
+ assert isinstance(obj, dict)
+ errors = from_list(from_str, obj.get("errors"))
+ warnings = from_list(from_str, obj.get("warnings"))
+ return SkillsLoadDiagnostics(errors, warnings)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["errors"] = from_list(from_str, self.errors)
+ result["warnings"] = from_list(from_str, self.warnings)
+ return result
+
+class SlashCommandAgentPromptResultKind(Enum):
+ AGENT_PROMPT = "agent-prompt"
+
+class SlashCommandCompletedResultKind(Enum):
+ COMPLETED = "completed"
+
+class SlashCommandInvocationResultKind(Enum):
+ AGENT_PROMPT = "agent-prompt"
+ COMPLETED = "completed"
+ TEXT = "text"
+
class TaskInfoExecutionMode(Enum):
"""How the agent is currently being managed by the runtime
@@ -2767,6 +2900,45 @@ def to_dict(self) -> dict:
result["statusMessage"] = from_union([from_str, from_none], self.status_message)
return result
+@dataclass
+class SlashCommandInput:
+ """Optional unstructured input hint"""
+
+ hint: str
+ """Hint to display when command input has not been provided"""
+
+ completion: SlashCommandInputCompletion | None = None
+ """Optional completion hint for the input (e.g. 'directory' for filesystem path completion)"""
+
+ preserve_multiline_input: bool | None = None
+ """When true, clients should pass the full text after the command name as a single argument
+ rather than splitting on whitespace
+ """
+ required: bool | None = None
+ """When true, the command requires non-empty input; clients should render the input hint as
+ required
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandInput':
+ assert isinstance(obj, dict)
+ hint = from_str(obj.get("hint"))
+ completion = from_union([SlashCommandInputCompletion, from_none], obj.get("completion"))
+ preserve_multiline_input = from_union([from_bool, from_none], obj.get("preserveMultilineInput"))
+ required = from_union([from_bool, from_none], obj.get("required"))
+ return SlashCommandInput(hint, completion, preserve_multiline_input, required)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["hint"] = from_str(self.hint)
+ if self.completion is not None:
+ result["completion"] = from_union([lambda x: to_enum(SlashCommandInputCompletion, x), from_none], self.completion)
+ if self.preserve_multiline_input is not None:
+ result["preserveMultilineInput"] = from_union([from_bool, from_none], self.preserve_multiline_input)
+ if self.required is not None:
+ result["required"] = from_union([from_bool, from_none], self.required)
+ return result
+
@dataclass
class CommandsRespondToQueuedCommandRequest:
request_id: str
@@ -3014,20 +3186,61 @@ class ExternalToolTextResultForLlmContentText:
text: str
"""The text content"""
- type: ExternalToolTextResultForLlmContentTextType
+ type: KindEnum
"""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"))
+ type = KindEnum(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)
+ result["type"] = to_enum(KindEnum, self.type)
+ return result
+
+@dataclass
+class SlashCommandTextResult:
+ kind: KindEnum
+ """Text result discriminator"""
+
+ text: str
+ """Text output for the client to render"""
+
+ markdown: bool | None = None
+ """Whether text contains Markdown"""
+
+ preserve_ansi: bool | None = None
+ """Whether ANSI sequences should be preserved"""
+
+ runtime_settings_changed: bool | None = None
+ """True when the invocation mutated user runtime settings; consumers caching settings should
+ refresh
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandTextResult':
+ assert isinstance(obj, dict)
+ kind = KindEnum(obj.get("kind"))
+ text = from_str(obj.get("text"))
+ markdown = from_union([from_bool, from_none], obj.get("markdown"))
+ preserve_ansi = from_union([from_bool, from_none], obj.get("preserveAnsi"))
+ runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
+ return SlashCommandTextResult(kind, text, markdown, preserve_ansi, runtime_settings_changed)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = to_enum(KindEnum, self.kind)
+ result["text"] = from_str(self.text)
+ if self.markdown is not None:
+ result["markdown"] = from_union([from_bool, from_none], self.markdown)
+ if self.preserve_ansi is not None:
+ result["preserveAnsi"] = from_union([from_bool, from_none], self.preserve_ansi)
+ if self.runtime_settings_changed is not None:
+ result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed)
return result
# Experimental: this type is part of an experimental API and may change or be removed.
@@ -3368,18 +3581,43 @@ def to_dict(self) -> dict:
@dataclass
class ModeSetRequest:
- mode: SessionMode
+ mode: Mode
"""The agent mode. Valid values: "interactive", "plan", "autopilot"."""
@staticmethod
def from_dict(obj: Any) -> 'ModeSetRequest':
assert isinstance(obj, dict)
- mode = SessionMode(obj.get("mode"))
+ mode = Mode(obj.get("mode"))
return ModeSetRequest(mode)
def to_dict(self) -> dict:
result: dict = {}
- result["mode"] = to_enum(SessionMode, self.mode)
+ result["mode"] = to_enum(Mode, self.mode)
+ return result
+
+@dataclass
+class ModelBilling:
+ """Billing information"""
+
+ multiplier: float | None = None
+ """Billing cost multiplier relative to the base rate"""
+
+ token_prices: ModelBillingTokenPrices | None = None
+ """Token-level pricing information for this model"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'ModelBilling':
+ assert isinstance(obj, dict)
+ multiplier = from_union([from_float, from_none], obj.get("multiplier"))
+ token_prices = from_union([ModelBillingTokenPrices.from_dict, from_none], obj.get("tokenPrices"))
+ return ModelBilling(multiplier, token_prices)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ if self.multiplier is not None:
+ result["multiplier"] = from_union([to_float, from_none], self.multiplier)
+ if self.token_prices is not None:
+ result["tokenPrices"] = from_union([lambda x: to_class(ModelBillingTokenPrices, x), from_none], self.token_prices)
return result
@dataclass
@@ -4096,6 +4334,145 @@ def to_dict(self) -> dict:
result["skills"] = from_list(lambda x: to_class(Skill, x), self.skills)
return result
+@dataclass
+class SlashCommandAgentPromptResult:
+ display_prompt: str
+ """Prompt text to display to the user"""
+
+ kind: SlashCommandAgentPromptResultKind
+ """Agent prompt result discriminator"""
+
+ prompt: str
+ """Prompt to submit to the agent"""
+
+ mode: Mode | None = None
+ """Optional target session mode"""
+
+ runtime_settings_changed: bool | None = None
+ """True when the invocation mutated user runtime settings; consumers caching settings should
+ refresh
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandAgentPromptResult':
+ assert isinstance(obj, dict)
+ display_prompt = from_str(obj.get("displayPrompt"))
+ kind = SlashCommandAgentPromptResultKind(obj.get("kind"))
+ prompt = from_str(obj.get("prompt"))
+ mode = from_union([Mode, from_none], obj.get("mode"))
+ runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
+ return SlashCommandAgentPromptResult(display_prompt, kind, prompt, mode, runtime_settings_changed)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["displayPrompt"] = from_str(self.display_prompt)
+ result["kind"] = to_enum(SlashCommandAgentPromptResultKind, self.kind)
+ result["prompt"] = from_str(self.prompt)
+ if self.mode is not None:
+ result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode)
+ if self.runtime_settings_changed is not None:
+ result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed)
+ return result
+
+@dataclass
+class SlashCommandCompletedResult:
+ kind: SlashCommandCompletedResultKind
+ """Completed result discriminator"""
+
+ message: str | None = None
+ """Optional user-facing message describing the completed command"""
+
+ runtime_settings_changed: bool | None = None
+ """True when the invocation mutated user runtime settings; consumers caching settings should
+ refresh
+ """
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandCompletedResult':
+ assert isinstance(obj, dict)
+ kind = SlashCommandCompletedResultKind(obj.get("kind"))
+ message = from_union([from_str, from_none], obj.get("message"))
+ runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
+ return SlashCommandCompletedResult(kind, message, runtime_settings_changed)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = to_enum(SlashCommandCompletedResultKind, self.kind)
+ if self.message is not None:
+ result["message"] = from_union([from_str, from_none], self.message)
+ if self.runtime_settings_changed is not None:
+ result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed)
+ return result
+
+@dataclass
+class SlashCommandInvocationResult:
+ kind: SlashCommandInvocationResultKind
+ """Text result discriminator
+
+ Agent prompt result discriminator
+
+ Completed result discriminator
+ """
+ markdown: bool | None = None
+ """Whether text contains Markdown"""
+
+ preserve_ansi: bool | None = None
+ """Whether ANSI sequences should be preserved"""
+
+ runtime_settings_changed: bool | None = None
+ """True when the invocation mutated user runtime settings; consumers caching settings should
+ refresh
+ """
+ text: str | None = None
+ """Text output for the client to render"""
+
+ display_prompt: str | None = None
+ """Prompt text to display to the user"""
+
+ mode: Mode | None = None
+ """Optional target session mode"""
+
+ prompt: str | None = None
+ """Prompt to submit to the agent"""
+
+ message: str | None = None
+ """Optional user-facing message describing the completed command"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandInvocationResult':
+ assert isinstance(obj, dict)
+ kind = SlashCommandInvocationResultKind(obj.get("kind"))
+ markdown = from_union([from_bool, from_none], obj.get("markdown"))
+ preserve_ansi = from_union([from_bool, from_none], obj.get("preserveAnsi"))
+ runtime_settings_changed = from_union([from_bool, from_none], obj.get("runtimeSettingsChanged"))
+ text = from_union([from_str, from_none], obj.get("text"))
+ display_prompt = from_union([from_str, from_none], obj.get("displayPrompt"))
+ mode = from_union([Mode, from_none], obj.get("mode"))
+ prompt = from_union([from_str, from_none], obj.get("prompt"))
+ message = from_union([from_str, from_none], obj.get("message"))
+ return SlashCommandInvocationResult(kind, markdown, preserve_ansi, runtime_settings_changed, text, display_prompt, mode, prompt, message)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["kind"] = to_enum(SlashCommandInvocationResultKind, self.kind)
+ if self.markdown is not None:
+ result["markdown"] = from_union([from_bool, from_none], self.markdown)
+ if self.preserve_ansi is not None:
+ result["preserveAnsi"] = from_union([from_bool, from_none], self.preserve_ansi)
+ if self.runtime_settings_changed is not None:
+ result["runtimeSettingsChanged"] = from_union([from_bool, from_none], self.runtime_settings_changed)
+ if self.text is not None:
+ result["text"] = from_union([from_str, from_none], self.text)
+ if self.display_prompt is not None:
+ result["displayPrompt"] = from_union([from_str, from_none], self.display_prompt)
+ if self.mode is not None:
+ result["mode"] = from_union([lambda x: to_enum(Mode, x), from_none], self.mode)
+ if self.prompt is not None:
+ result["prompt"] = from_union([from_str, from_none], self.prompt)
+ if self.message is not None:
+ result["message"] = from_union([from_str, from_none], self.message)
+ return result
+
@dataclass
class TaskShellInfo:
attachment_mode: TaskShellInfoAttachmentMode
@@ -4543,6 +4920,56 @@ def to_dict(self) -> dict:
result["user_named"] = from_union([from_bool, from_none], self.user_named)
return result
+@dataclass
+class SlashCommandInfo:
+ allow_during_agent_execution: bool
+ """Whether the command may run while an agent turn is active"""
+
+ description: str
+ """Human-readable command description"""
+
+ kind: SlashCommandKind
+ """Coarse command category for grouping and behavior: runtime built-in, skill-backed
+ command, or SDK/client-owned command
+ """
+ name: str
+ """Canonical command name without a leading slash"""
+
+ aliases: list[str] | None = None
+ """Canonical aliases without leading slashes"""
+
+ experimental: bool | None = None
+ """Whether the command is experimental"""
+
+ input: SlashCommandInput | None = None
+ """Optional unstructured input hint"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'SlashCommandInfo':
+ assert isinstance(obj, dict)
+ allow_during_agent_execution = from_bool(obj.get("allowDuringAgentExecution"))
+ description = from_str(obj.get("description"))
+ kind = SlashCommandKind(obj.get("kind"))
+ name = from_str(obj.get("name"))
+ aliases = from_union([lambda x: from_list(from_str, x), from_none], obj.get("aliases"))
+ experimental = from_union([from_bool, from_none], obj.get("experimental"))
+ input = from_union([SlashCommandInput.from_dict, from_none], obj.get("input"))
+ return SlashCommandInfo(allow_during_agent_execution, description, kind, name, aliases, experimental, input)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["allowDuringAgentExecution"] = from_bool(self.allow_during_agent_execution)
+ result["description"] = from_str(self.description)
+ result["kind"] = to_enum(SlashCommandKind, self.kind)
+ result["name"] = from_str(self.name)
+ if self.aliases is not None:
+ result["aliases"] = from_union([lambda x: from_list(from_str, x), from_none], self.aliases)
+ if self.experimental is not None:
+ result["experimental"] = from_union([from_bool, from_none], self.experimental)
+ if self.input is not None:
+ result["input"] = from_union([lambda x: to_class(SlashCommandInput, x), from_none], self.input)
+ return result
+
@dataclass
class MCPDiscoverResult:
servers: list[DiscoveredMCPServer]
@@ -5331,6 +5758,22 @@ def to_dict(self) -> dict:
result["workspace"] = from_union([lambda x: to_class(Workspace, x), from_none], self.workspace)
return result
+@dataclass
+class CommandList:
+ commands: list[SlashCommandInfo]
+ """Commands available in this session"""
+
+ @staticmethod
+ def from_dict(obj: Any) -> 'CommandList':
+ assert isinstance(obj, dict)
+ commands = from_list(SlashCommandInfo.from_dict, obj.get("commands"))
+ return CommandList(commands)
+
+ def to_dict(self) -> dict:
+ result: dict = {}
+ result["commands"] = from_list(lambda x: to_class(SlashCommandInfo, x), self.commands)
+ return result
+
@dataclass
class ExternalToolTextResultForLlm:
"""Expanded external tool result payload"""
@@ -5903,8 +6346,11 @@ class RPC:
agent_select_request: AgentSelectRequest
agent_select_result: AgentSelectResult
auth_info_type: AuthInfoType
+ command_list: CommandList
commands_handle_pending_command_request: CommandsHandlePendingCommandRequest
commands_handle_pending_command_result: CommandsHandlePendingCommandResult
+ commands_invoke_request: CommandsInvokeRequest
+ commands_list_request: CommandsListRequest
commands_respond_to_queued_command_request: CommandsRespondToQueuedCommandRequest
commands_respond_to_queued_command_result: CommandsRespondToQueuedCommandResult
connect_request: ConnectRequest
@@ -5974,6 +6420,7 @@ class RPC:
mcp_server_status: MCPServerStatus
model: Model
model_billing: ModelBilling
+ model_billing_token_prices: ModelBillingTokenPrices
model_capabilities: ModelCapabilities
model_capabilities_limits: ModelCapabilitiesLimits
model_capabilities_limits_vision: ModelCapabilitiesLimitsVision
@@ -6061,7 +6508,7 @@ class RPC:
session_fs_stat_result: SessionFSStatResult
session_fs_write_file_request: SessionFSWriteFileRequest
session_log_level: SessionLogLevel
- session_mode: SessionMode
+ session_mode: Mode
sessions_fork_request: SessionsForkRequest
sessions_fork_result: SessionsForkResult
shell_exec_request: ShellExecRequest
@@ -6075,6 +6522,16 @@ class RPC:
skills_disable_request: SkillsDisableRequest
skills_discover_request: SkillsDiscoverRequest
skills_enable_request: SkillsEnableRequest
+ skills_load_diagnostics: SkillsLoadDiagnostics
+ slash_command_agent_prompt_mode: Mode
+ slash_command_agent_prompt_result: SlashCommandAgentPromptResult
+ slash_command_completed_result: SlashCommandCompletedResult
+ slash_command_info: SlashCommandInfo
+ slash_command_input: SlashCommandInput
+ slash_command_input_completion: SlashCommandInputCompletion
+ slash_command_invocation_result: SlashCommandInvocationResult
+ slash_command_kind: SlashCommandKind
+ slash_command_text_result: SlashCommandTextResult
task_agent_info: TaskAgentInfo
task_agent_info_execution_mode: TaskInfoExecutionMode
task_agent_info_status: TaskInfoStatus
@@ -6145,8 +6602,11 @@ def from_dict(obj: Any) -> 'RPC':
agent_select_request = AgentSelectRequest.from_dict(obj.get("AgentSelectRequest"))
agent_select_result = AgentSelectResult.from_dict(obj.get("AgentSelectResult"))
auth_info_type = AuthInfoType(obj.get("AuthInfoType"))
+ command_list = CommandList.from_dict(obj.get("CommandList"))
commands_handle_pending_command_request = CommandsHandlePendingCommandRequest.from_dict(obj.get("CommandsHandlePendingCommandRequest"))
commands_handle_pending_command_result = CommandsHandlePendingCommandResult.from_dict(obj.get("CommandsHandlePendingCommandResult"))
+ commands_invoke_request = CommandsInvokeRequest.from_dict(obj.get("CommandsInvokeRequest"))
+ commands_list_request = CommandsListRequest.from_dict(obj.get("CommandsListRequest"))
commands_respond_to_queued_command_request = CommandsRespondToQueuedCommandRequest.from_dict(obj.get("CommandsRespondToQueuedCommandRequest"))
commands_respond_to_queued_command_result = CommandsRespondToQueuedCommandResult.from_dict(obj.get("CommandsRespondToQueuedCommandResult"))
connect_request = ConnectRequest.from_dict(obj.get("ConnectRequest"))
@@ -6216,6 +6676,7 @@ def from_dict(obj: Any) -> 'RPC':
mcp_server_status = MCPServerStatus(obj.get("McpServerStatus"))
model = Model.from_dict(obj.get("Model"))
model_billing = ModelBilling.from_dict(obj.get("ModelBilling"))
+ model_billing_token_prices = ModelBillingTokenPrices.from_dict(obj.get("ModelBillingTokenPrices"))
model_capabilities = ModelCapabilities.from_dict(obj.get("ModelCapabilities"))
model_capabilities_limits = ModelCapabilitiesLimits.from_dict(obj.get("ModelCapabilitiesLimits"))
model_capabilities_limits_vision = ModelCapabilitiesLimitsVision.from_dict(obj.get("ModelCapabilitiesLimitsVision"))
@@ -6303,7 +6764,7 @@ def from_dict(obj: Any) -> 'RPC':
session_fs_stat_result = SessionFSStatResult.from_dict(obj.get("SessionFsStatResult"))
session_fs_write_file_request = SessionFSWriteFileRequest.from_dict(obj.get("SessionFsWriteFileRequest"))
session_log_level = SessionLogLevel(obj.get("SessionLogLevel"))
- session_mode = SessionMode(obj.get("SessionMode"))
+ session_mode = Mode(obj.get("SessionMode"))
sessions_fork_request = SessionsForkRequest.from_dict(obj.get("SessionsForkRequest"))
sessions_fork_result = SessionsForkResult.from_dict(obj.get("SessionsForkResult"))
shell_exec_request = ShellExecRequest.from_dict(obj.get("ShellExecRequest"))
@@ -6317,6 +6778,16 @@ def from_dict(obj: Any) -> 'RPC':
skills_disable_request = SkillsDisableRequest.from_dict(obj.get("SkillsDisableRequest"))
skills_discover_request = SkillsDiscoverRequest.from_dict(obj.get("SkillsDiscoverRequest"))
skills_enable_request = SkillsEnableRequest.from_dict(obj.get("SkillsEnableRequest"))
+ skills_load_diagnostics = SkillsLoadDiagnostics.from_dict(obj.get("SkillsLoadDiagnostics"))
+ slash_command_agent_prompt_mode = Mode(obj.get("SlashCommandAgentPromptMode"))
+ slash_command_agent_prompt_result = SlashCommandAgentPromptResult.from_dict(obj.get("SlashCommandAgentPromptResult"))
+ slash_command_completed_result = SlashCommandCompletedResult.from_dict(obj.get("SlashCommandCompletedResult"))
+ slash_command_info = SlashCommandInfo.from_dict(obj.get("SlashCommandInfo"))
+ slash_command_input = SlashCommandInput.from_dict(obj.get("SlashCommandInput"))
+ slash_command_input_completion = SlashCommandInputCompletion(obj.get("SlashCommandInputCompletion"))
+ slash_command_invocation_result = SlashCommandInvocationResult.from_dict(obj.get("SlashCommandInvocationResult"))
+ slash_command_kind = SlashCommandKind(obj.get("SlashCommandKind"))
+ slash_command_text_result = SlashCommandTextResult.from_dict(obj.get("SlashCommandTextResult"))
task_agent_info = TaskAgentInfo.from_dict(obj.get("TaskAgentInfo"))
task_agent_info_execution_mode = TaskInfoExecutionMode(obj.get("TaskAgentInfoExecutionMode"))
task_agent_info_status = TaskInfoStatus(obj.get("TaskAgentInfoStatus"))
@@ -6373,7 +6844,7 @@ def from_dict(obj: Any) -> 'RPC':
workspaces_list_files_result = WorkspacesListFilesResult.from_dict(obj.get("WorkspacesListFilesResult"))
workspaces_read_file_request = WorkspacesReadFileRequest.from_dict(obj.get("WorkspacesReadFileRequest"))
workspaces_read_file_result = WorkspacesReadFileResult.from_dict(obj.get("WorkspacesReadFileResult"))
- return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connect_request, connect_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_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_picker_category, model_picker_price_category, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, remote_enable_result, 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_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_list, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result)
+ return RPC(account_get_quota_request, account_get_quota_result, account_quota_snapshot, agent_get_current_result, agent_info, agent_list, agent_reload_result, agent_select_request, agent_select_result, auth_info_type, command_list, commands_handle_pending_command_request, commands_handle_pending_command_result, commands_invoke_request, commands_list_request, commands_respond_to_queued_command_request, commands_respond_to_queued_command_result, connect_request, connect_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_oauth_grant_type, mcp_server_config_http_type, mcp_server_config_local, mcp_server_config_local_type, mcp_server_list, mcp_server_source, mcp_server_status, model, model_billing, model_billing_token_prices, model_capabilities, model_capabilities_limits, model_capabilities_limits_vision, model_capabilities_override, model_capabilities_override_limits, model_capabilities_override_limits_vision, model_capabilities_override_supports, model_capabilities_supports, model_list, model_picker_category, model_picker_price_category, model_policy, models_list_request, model_switch_to_request, model_switch_to_result, mode_set_request, name_get_result, name_set_request, permission_decision, permission_decision_approve_for_location, permission_decision_approve_for_location_approval, permission_decision_approve_for_location_approval_commands, permission_decision_approve_for_location_approval_custom_tool, permission_decision_approve_for_location_approval_extension_management, permission_decision_approve_for_location_approval_extension_permission_access, permission_decision_approve_for_location_approval_mcp, permission_decision_approve_for_location_approval_mcp_sampling, permission_decision_approve_for_location_approval_memory, permission_decision_approve_for_location_approval_read, permission_decision_approve_for_location_approval_write, permission_decision_approve_for_session, permission_decision_approve_for_session_approval, permission_decision_approve_for_session_approval_commands, permission_decision_approve_for_session_approval_custom_tool, permission_decision_approve_for_session_approval_extension_management, permission_decision_approve_for_session_approval_extension_permission_access, permission_decision_approve_for_session_approval_mcp, permission_decision_approve_for_session_approval_mcp_sampling, permission_decision_approve_for_session_approval_memory, permission_decision_approve_for_session_approval_read, permission_decision_approve_for_session_approval_write, permission_decision_approve_once, permission_decision_approve_permanently, permission_decision_reject, permission_decision_request, permission_decision_user_not_available, permission_request_result, permissions_reset_session_approvals_request, permissions_reset_session_approvals_result, permissions_set_approve_all_request, permissions_set_approve_all_result, ping_request, ping_result, plan_read_result, plan_update_request, plugin, plugin_list, queued_command_handled, queued_command_not_handled, queued_command_result, remote_enable_result, server_skill, server_skill_list, session_auth_status, session_fs_append_file_request, session_fs_error, session_fs_error_code, session_fs_exists_request, session_fs_exists_result, session_fs_mkdir_request, session_fs_readdir_request, session_fs_readdir_result, session_fs_readdir_with_types_entry, session_fs_readdir_with_types_entry_type, session_fs_readdir_with_types_request, session_fs_readdir_with_types_result, session_fs_read_file_request, session_fs_read_file_result, session_fs_rename_request, session_fs_rm_request, session_fs_set_provider_conventions, session_fs_set_provider_request, session_fs_set_provider_result, session_fs_stat_request, session_fs_stat_result, session_fs_write_file_request, session_log_level, session_mode, sessions_fork_request, sessions_fork_result, shell_exec_request, shell_exec_result, shell_kill_request, shell_kill_result, shell_kill_signal, skill, skill_list, skills_config_set_disabled_skills_request, skills_disable_request, skills_discover_request, skills_enable_request, skills_load_diagnostics, slash_command_agent_prompt_mode, slash_command_agent_prompt_result, slash_command_completed_result, slash_command_info, slash_command_input, slash_command_input_completion, slash_command_invocation_result, slash_command_kind, slash_command_text_result, task_agent_info, task_agent_info_execution_mode, task_agent_info_status, task_info, task_list, tasks_cancel_request, tasks_cancel_result, task_shell_info, task_shell_info_attachment_mode, task_shell_info_execution_mode, task_shell_info_status, tasks_promote_to_background_request, tasks_promote_to_background_result, tasks_remove_request, tasks_remove_result, tasks_send_message_request, tasks_send_message_result, tasks_start_agent_request, tasks_start_agent_result, tool, tool_list, tools_list_request, ui_elicitation_array_any_of_field, ui_elicitation_array_any_of_field_items, ui_elicitation_array_any_of_field_items_any_of, ui_elicitation_array_enum_field, ui_elicitation_array_enum_field_items, ui_elicitation_field_value, ui_elicitation_request, ui_elicitation_response, ui_elicitation_response_action, ui_elicitation_response_content, ui_elicitation_result, ui_elicitation_schema, ui_elicitation_schema_property, ui_elicitation_schema_property_boolean, ui_elicitation_schema_property_number, ui_elicitation_schema_property_number_type, ui_elicitation_schema_property_string, ui_elicitation_schema_property_string_format, ui_elicitation_string_enum_field, ui_elicitation_string_one_of_field, ui_elicitation_string_one_of_field_one_of, ui_handle_pending_elicitation_request, usage_get_metrics_result, usage_metrics_code_changes, usage_metrics_model_metric, usage_metrics_model_metric_requests, usage_metrics_model_metric_token_detail, usage_metrics_model_metric_usage, usage_metrics_token_detail, workspaces_create_file_request, workspaces_get_workspace_result, workspaces_list_files_result, workspaces_read_file_request, workspaces_read_file_result)
def to_dict(self) -> dict:
result: dict = {}
@@ -6387,8 +6858,11 @@ def to_dict(self) -> dict:
result["AgentSelectRequest"] = to_class(AgentSelectRequest, self.agent_select_request)
result["AgentSelectResult"] = to_class(AgentSelectResult, self.agent_select_result)
result["AuthInfoType"] = to_enum(AuthInfoType, self.auth_info_type)
+ result["CommandList"] = to_class(CommandList, self.command_list)
result["CommandsHandlePendingCommandRequest"] = to_class(CommandsHandlePendingCommandRequest, self.commands_handle_pending_command_request)
result["CommandsHandlePendingCommandResult"] = to_class(CommandsHandlePendingCommandResult, self.commands_handle_pending_command_result)
+ result["CommandsInvokeRequest"] = to_class(CommandsInvokeRequest, self.commands_invoke_request)
+ result["CommandsListRequest"] = to_class(CommandsListRequest, self.commands_list_request)
result["CommandsRespondToQueuedCommandRequest"] = to_class(CommandsRespondToQueuedCommandRequest, self.commands_respond_to_queued_command_request)
result["CommandsRespondToQueuedCommandResult"] = to_class(CommandsRespondToQueuedCommandResult, self.commands_respond_to_queued_command_result)
result["ConnectRequest"] = to_class(ConnectRequest, self.connect_request)
@@ -6458,6 +6932,7 @@ def to_dict(self) -> dict:
result["McpServerStatus"] = to_enum(MCPServerStatus, self.mcp_server_status)
result["Model"] = to_class(Model, self.model)
result["ModelBilling"] = to_class(ModelBilling, self.model_billing)
+ result["ModelBillingTokenPrices"] = to_class(ModelBillingTokenPrices, self.model_billing_token_prices)
result["ModelCapabilities"] = to_class(ModelCapabilities, self.model_capabilities)
result["ModelCapabilitiesLimits"] = to_class(ModelCapabilitiesLimits, self.model_capabilities_limits)
result["ModelCapabilitiesLimitsVision"] = to_class(ModelCapabilitiesLimitsVision, self.model_capabilities_limits_vision)
@@ -6545,7 +7020,7 @@ def to_dict(self) -> dict:
result["SessionFsStatResult"] = to_class(SessionFSStatResult, self.session_fs_stat_result)
result["SessionFsWriteFileRequest"] = to_class(SessionFSWriteFileRequest, self.session_fs_write_file_request)
result["SessionLogLevel"] = to_enum(SessionLogLevel, self.session_log_level)
- result["SessionMode"] = to_enum(SessionMode, self.session_mode)
+ result["SessionMode"] = to_enum(Mode, self.session_mode)
result["SessionsForkRequest"] = to_class(SessionsForkRequest, self.sessions_fork_request)
result["SessionsForkResult"] = to_class(SessionsForkResult, self.sessions_fork_result)
result["ShellExecRequest"] = to_class(ShellExecRequest, self.shell_exec_request)
@@ -6559,6 +7034,16 @@ def to_dict(self) -> dict:
result["SkillsDisableRequest"] = to_class(SkillsDisableRequest, self.skills_disable_request)
result["SkillsDiscoverRequest"] = to_class(SkillsDiscoverRequest, self.skills_discover_request)
result["SkillsEnableRequest"] = to_class(SkillsEnableRequest, self.skills_enable_request)
+ result["SkillsLoadDiagnostics"] = to_class(SkillsLoadDiagnostics, self.skills_load_diagnostics)
+ result["SlashCommandAgentPromptMode"] = to_enum(Mode, self.slash_command_agent_prompt_mode)
+ result["SlashCommandAgentPromptResult"] = to_class(SlashCommandAgentPromptResult, self.slash_command_agent_prompt_result)
+ result["SlashCommandCompletedResult"] = to_class(SlashCommandCompletedResult, self.slash_command_completed_result)
+ result["SlashCommandInfo"] = to_class(SlashCommandInfo, self.slash_command_info)
+ result["SlashCommandInput"] = to_class(SlashCommandInput, self.slash_command_input)
+ result["SlashCommandInputCompletion"] = to_enum(SlashCommandInputCompletion, self.slash_command_input_completion)
+ result["SlashCommandInvocationResult"] = to_class(SlashCommandInvocationResult, self.slash_command_invocation_result)
+ result["SlashCommandKind"] = to_enum(SlashCommandKind, self.slash_command_kind)
+ result["SlashCommandTextResult"] = to_class(SlashCommandTextResult, self.slash_command_text_result)
result["TaskAgentInfo"] = to_class(TaskAgentInfo, self.task_agent_info)
result["TaskAgentInfoExecutionMode"] = to_enum(TaskInfoExecutionMode, self.task_agent_info_execution_mode)
result["TaskAgentInfoStatus"] = to_enum(TaskInfoStatus, self.task_agent_info_status)
@@ -6624,6 +7109,17 @@ def rpc_to_dict(x: RPC) -> Any:
return to_class(RPC, x)
+DiscoveredMcpServerSource = MCPServerSource
+ExternalToolResult = ExternalToolTextResultForLlm
+FilterMapping = dict
+FilterMappingValue = FilterMappingString
+SessionMode = Mode
+SlashCommandAgentPromptMode = Mode
+TaskAgentInfoExecutionMode = TaskInfoExecutionMode
+TaskAgentInfoStatus = TaskInfoStatus
+TaskShellInfoExecutionMode = TaskInfoExecutionMode
+TaskShellInfoStatus = TaskInfoStatus
+
def _timeout_kwargs(timeout: float | None) -> dict:
"""Build keyword arguments for optional timeout forwarding."""
if timeout is not None:
@@ -6773,7 +7269,7 @@ async def ping(self, params: PingRequest, *, timeout: float | None = None) -> Pi
class _InternalServerRpc:
- """Internal SDK server-scoped RPC methods (handshake helpers etc.). Not part of the public API."""
+ """Internal SDK server-scoped RPC methods. Not part of the public API."""
def __init__(self, client: "JsonRpcClient"):
self._client = client
@@ -6811,8 +7307,8 @@ def __init__(self, client: "JsonRpcClient", session_id: str):
self._client = client
self._session_id = session_id
- async def get(self, *, timeout: float | None = None) -> SessionMode:
- return SessionMode(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
+ async def get(self, *, timeout: float | None = None) -> Mode:
+ return Mode(await self._client.request("session.mode.get", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
async def set(self, params: ModeSetRequest, *, timeout: float | None = None) -> None:
params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None}
@@ -6972,8 +7468,8 @@ async def disable(self, params: SkillsDisableRequest, *, timeout: float | None =
params_dict["sessionId"] = self._session_id
await self._client.request("session.skills.disable", params_dict, **_timeout_kwargs(timeout))
- async def reload(self, *, timeout: float | None = None) -> None:
- await self._client.request("session.skills.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout))
+ async def reload(self, *, timeout: float | None = None) -> SkillsLoadDiagnostics:
+ return SkillsLoadDiagnostics.from_dict(await self._client.request("session.skills.reload", {"sessionId": self._session_id}, **_timeout_kwargs(timeout)))
# Experimental: this API group is experimental and may change or be removed.
@@ -7061,6 +7557,16 @@ def __init__(self, client: "JsonRpcClient", session_id: str):
self._client = client
self._session_id = session_id
+ async def list(self, params: CommandsListRequest | None = None, *, timeout: float | None = None) -> CommandList:
+ params_dict: dict[str, Any] = {k: v for k, v in params.to_dict().items() if v is not None} if params is not None else {}
+ params_dict["sessionId"] = self._session_id
+ return CommandList.from_dict(await self._client.request("session.commands.list", params_dict, **_timeout_kwargs(timeout)))
+
+ async def invoke(self, params: CommandsInvokeRequest, *, timeout: float | None = None) -> SlashCommandInvocationResult:
+ 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 SlashCommandInvocationResult.from_dict(await self._client.request("session.commands.invoke", params_dict, **_timeout_kwargs(timeout)))
+
async def handle_pending_command(self, params: CommandsHandlePendingCommandRequest, *, timeout: float | None = None) -> CommandsHandlePendingCommandResult:
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
diff --git a/python/copilot/generated/session_events.py b/python/copilot/generated/session_events.py
index 4f3e791ce..9d297887b 100644
--- a/python/copilot/generated/session_events.py
+++ b/python/copilot/generated/session_events.py
@@ -657,6 +657,7 @@ class AssistantUsageData:
"LLM API call usage metrics including tokens, costs, quotas, and billing information"
model: str
api_call_id: str | None = None
+ api_endpoint: AssistantUsageApiEndpoint | None = None
cache_read_tokens: float | None = None
cache_write_tokens: float | None = None
copilot_usage: AssistantUsageCopilotUsage | None = None
@@ -679,6 +680,7 @@ def from_dict(obj: Any) -> "AssistantUsageData":
assert isinstance(obj, dict)
model = from_str(obj.get("model"))
api_call_id = from_union([from_none, from_str], obj.get("apiCallId"))
+ api_endpoint = from_union([from_none, lambda x: parse_enum(AssistantUsageApiEndpoint, x)], obj.get("apiEndpoint"))
cache_read_tokens = from_union([from_none, from_float], obj.get("cacheReadTokens"))
cache_write_tokens = from_union([from_none, from_float], obj.get("cacheWriteTokens"))
copilot_usage = from_union([from_none, AssistantUsageCopilotUsage.from_dict], obj.get("copilotUsage"))
@@ -697,6 +699,7 @@ def from_dict(obj: Any) -> "AssistantUsageData":
return AssistantUsageData(
model=model,
api_call_id=api_call_id,
+ api_endpoint=api_endpoint,
cache_read_tokens=cache_read_tokens,
cache_write_tokens=cache_write_tokens,
copilot_usage=copilot_usage,
@@ -719,6 +722,8 @@ def to_dict(self) -> dict:
result["model"] = from_str(self.model)
if self.api_call_id is not None:
result["apiCallId"] = from_union([from_none, from_str], self.api_call_id)
+ if self.api_endpoint is not None:
+ result["apiEndpoint"] = from_union([from_none, lambda x: to_enum(AssistantUsageApiEndpoint, x)], self.api_endpoint)
if self.cache_read_tokens is not None:
result["cacheReadTokens"] = from_union([from_none, to_float], self.cache_read_tokens)
if self.cache_write_tokens is not None:
@@ -4500,6 +4505,7 @@ class UserMessageData:
agent_mode: UserMessageAgentMode | None = None
attachments: list[UserMessageAttachment] | None = None
interaction_id: str | None = None
+ is_autopilot_continuation: bool | None = None
native_document_path_fallback_paths: list[str] | None = None
parent_agent_task_id: str | None = None
source: str | None = None
@@ -4513,6 +4519,7 @@ def from_dict(obj: Any) -> "UserMessageData":
agent_mode = from_union([from_none, lambda x: parse_enum(UserMessageAgentMode, x)], obj.get("agentMode"))
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"))
+ is_autopilot_continuation = from_union([from_none, from_bool], obj.get("isAutopilotContinuation"))
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"))
@@ -4523,6 +4530,7 @@ def from_dict(obj: Any) -> "UserMessageData":
agent_mode=agent_mode,
attachments=attachments,
interaction_id=interaction_id,
+ is_autopilot_continuation=is_autopilot_continuation,
native_document_path_fallback_paths=native_document_path_fallback_paths,
parent_agent_task_id=parent_agent_task_id,
source=source,
@@ -4539,6 +4547,8 @@ def to_dict(self) -> dict:
result["attachments"] = from_union([from_none, lambda x: from_list(lambda x: to_class(UserMessageAttachment, x), x)], self.attachments)
if self.interaction_id is not None:
result["interactionId"] = from_union([from_none, from_str], self.interaction_id)
+ if self.is_autopilot_continuation is not None:
+ result["isAutopilotContinuation"] = from_union([from_none, from_bool], self.is_autopilot_continuation)
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:
@@ -4663,6 +4673,14 @@ class AssistantMessageToolRequestType(Enum):
CUSTOM = "custom"
+class AssistantUsageApiEndpoint(Enum):
+ "API endpoint used for this model call, matching CAPI supported_endpoints vocabulary"
+ CHAT_COMPLETIONS = "/chat/completions"
+ V1_MESSAGES = "/v1/messages"
+ RESPONSES = "/responses"
+ WS_RESPONSES = "ws:/responses"
+
+
class ElicitationCompletedAction(Enum):
"The user action: \"accept\" (submitted form), \"decline\" (explicitly refused), or \"cancel\" (dismissed)"
ACCEPT = "accept"
diff --git a/python/test_rpc_generated.py b/python/test_rpc_generated.py
new file mode 100644
index 000000000..5f484add0
--- /dev/null
+++ b/python/test_rpc_generated.py
@@ -0,0 +1,24 @@
+"""Tests for generated RPC method behavior."""
+
+from unittest.mock import AsyncMock
+
+import pytest
+
+from copilot.generated.rpc import (
+ CommandsApi,
+ CommandsInvokeRequest,
+ SlashCommandInvocationResultKind,
+)
+
+
+@pytest.mark.asyncio
+async def test_commands_invoke_deserializes_slash_command_result():
+ client = AsyncMock()
+ client.request = AsyncMock(return_value={"kind": "text", "text": "hello", "markdown": True})
+ api = CommandsApi(client, "sess-1")
+
+ result = await api.invoke(CommandsInvokeRequest(name="help"))
+
+ assert result.kind is SlashCommandInvocationResultKind.TEXT
+ assert result.text == "hello"
+ assert result.markdown is True
diff --git a/rust/src/generated/api_types.rs b/rust/src/generated/api_types.rs
index 0fcaf1e2f..c6340ff55 100644
--- a/rust/src/generated/api_types.rs
+++ b/rust/src/generated/api_types.rs
@@ -128,6 +128,10 @@ pub mod rpc_methods {
pub const SESSION_EXTENSIONS_RELOAD: &str = "session.extensions.reload";
/// `session.tools.handlePendingToolCall`
pub const SESSION_TOOLS_HANDLEPENDINGTOOLCALL: &str = "session.tools.handlePendingToolCall";
+ /// `session.commands.list`
+ pub const SESSION_COMMANDS_LIST: &str = "session.commands.list";
+ /// `session.commands.invoke`
+ pub const SESSION_COMMANDS_INVOKE: &str = "session.commands.invoke";
/// `session.commands.handlePendingCommand`
pub const SESSION_COMMANDS_HANDLEPENDINGCOMMAND: &str = "session.commands.handlePendingCommand";
/// `session.commands.respondToQueuedCommand`
@@ -269,6 +273,52 @@ pub struct AgentSelectResult {
pub agent: AgentInfo,
}
+/// Optional unstructured input hint
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SlashCommandInput {
+ /// Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub completion: Option,
+ /// Hint to display when command input has not been provided
+ pub hint: String,
+ /// When true, clients should pass the full text after the command name as a single argument rather than splitting on whitespace
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub preserve_multiline_input: Option,
+ /// When true, the command requires non-empty input; clients should render the input hint as required
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub required: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SlashCommandInfo {
+ /// Canonical aliases without leading slashes
+ #[serde(default)]
+ pub aliases: Vec,
+ /// Whether the command may run while an agent turn is active
+ pub allow_during_agent_execution: bool,
+ /// Human-readable command description
+ pub description: String,
+ /// Whether the command is experimental
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub experimental: Option,
+ /// Optional unstructured input hint
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub input: Option,
+ /// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command
+ pub kind: SlashCommandKind,
+ /// Canonical command name without a leading slash
+ pub name: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CommandList {
+ /// Commands available in this session
+ pub commands: Vec,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandsHandlePendingCommandRequest {
@@ -286,6 +336,30 @@ pub struct CommandsHandlePendingCommandResult {
pub success: bool,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CommandsInvokeRequest {
+ /// Raw input after the command name
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub input: Option,
+ /// Command name. Leading slashes are stripped and the name is matched case-insensitively.
+ pub name: String,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct CommandsListRequest {
+ /// Include runtime built-in commands
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub include_builtins: Option,
+ /// Include commands registered by protocol clients, including SDK clients and extensions
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub include_client_commands: Option,
+ /// Include enabled user-invocable skills and commands
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub include_skills: Option,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandsRespondToQueuedCommandRequest {
@@ -839,6 +913,24 @@ pub struct McpServerList {
pub servers: Vec,
}
+/// Token-level pricing information for this model
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ModelBillingTokenPrices {
+ /// Number of tokens per standard billing batch
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub batch_size: Option,
+ /// Price per billing batch of cached tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub cache_price: Option,
+ /// Price per billing batch of input tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub input_price: Option,
+ /// Price per billing batch of output tokens in nano-AIUs (1 nano-AIU = 0.000000001 AIU, 1 AIU = $0.01 USD)
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub output_price: Option,
+}
+
/// Billing information
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
@@ -846,6 +938,9 @@ pub struct ModelBilling {
/// Billing cost multiplier relative to the base rate
#[serde(skip_serializing_if = "Option::is_none")]
pub multiplier: Option,
+ /// Token-level pricing information for this model
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub token_prices: Option,
}
/// Vision-specific limits
@@ -1608,6 +1703,9 @@ pub struct SessionFsWriteFileRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionsForkRequest {
+ /// Optional friendly name to assign to the forked session.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
/// Source session ID to fork from
pub session_id: SessionId,
/// Optional event ID boundary. When provided, the fork includes only events before this ID (exclusive). When omitted, all events are included.
@@ -1618,6 +1716,9 @@ pub struct SessionsForkRequest {
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionsForkResult {
+ /// Friendly name assigned to the forked session, if any.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub name: Option,
/// The new forked session's ID
pub session_id: SessionId,
}
@@ -1716,6 +1817,63 @@ pub struct SkillsEnableRequest {
pub name: String,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SkillsLoadDiagnostics {
+ /// Errors emitted while loading skills (e.g. skills that failed to load entirely)
+ pub errors: Vec,
+ /// Warnings emitted while loading skills (e.g. skills that loaded but had issues)
+ pub warnings: Vec,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SlashCommandAgentPromptResult {
+ /// Prompt text to display to the user
+ pub display_prompt: String,
+ /// Agent prompt result discriminator
+ pub kind: SlashCommandAgentPromptResultKind,
+ /// Optional target session mode
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub mode: Option,
+ /// Prompt to submit to the agent
+ pub prompt: String,
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub runtime_settings_changed: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SlashCommandCompletedResult {
+ /// Completed result discriminator
+ pub kind: SlashCommandCompletedResultKind,
+ /// Optional user-facing message describing the completed command
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub message: Option,
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub runtime_settings_changed: Option,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SlashCommandTextResult {
+ /// Text result discriminator
+ pub kind: SlashCommandTextResultKind,
+ /// Whether text contains Markdown
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub markdown: Option,
+ /// Whether ANSI sequences should be preserved
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub preserve_ansi: Option,
+ /// True when the invocation mutated user runtime settings; consumers caching settings should refresh
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub runtime_settings_changed: Option,
+ /// Text output for the client to render
+ pub text: String,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TaskAgentInfo {
@@ -2644,6 +2802,15 @@ pub struct SessionSkillsReloadParams {
pub session_id: SessionId,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SessionSkillsReloadResult {
+ /// Errors emitted while loading skills (e.g. skills that failed to load entirely)
+ pub errors: Vec,
+ /// Warnings emitted while loading skills (e.g. skills that loaded but had issues)
+ pub warnings: Vec,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionMcpListParams {
@@ -2715,6 +2882,13 @@ pub struct SessionToolsHandlePendingToolCallResult {
pub success: bool,
}
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct SessionCommandsListResult {
+ /// Commands available in this session
+ pub commands: Vec,
+}
+
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SessionCommandsHandlePendingCommandResult {
@@ -2900,6 +3074,30 @@ pub enum AuthInfoType {
Unknown,
}
+/// Optional completion hint for the input (e.g. 'directory' for filesystem path completion)
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandInputCompletion {
+ #[serde(rename = "directory")]
+ Directory,
+ /// Unknown variant for forward compatibility.
+ #[serde(other)]
+ Unknown,
+}
+
+/// Coarse command category for grouping and behavior: runtime built-in, skill-backed command, or SDK/client-owned command
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandKind {
+ #[serde(rename = "builtin")]
+ Builtin,
+ #[serde(rename = "skill")]
+ Skill,
+ #[serde(rename = "client")]
+ Client,
+ /// Unknown variant for forward compatibility.
+ #[serde(other)]
+ Unknown,
+}
+
/// Configuration source
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiscoveredMcpServerSource {
@@ -3440,6 +3638,49 @@ pub enum ShellKillSignal {
Unknown,
}
+/// Optional target session mode
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandAgentPromptMode {
+ #[serde(rename = "interactive")]
+ Interactive,
+ #[serde(rename = "plan")]
+ Plan,
+ #[serde(rename = "autopilot")]
+ Autopilot,
+ /// Unknown variant for forward compatibility.
+ #[serde(other)]
+ Unknown,
+}
+
+/// Agent prompt result discriminator
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandAgentPromptResultKind {
+ #[serde(rename = "agent-prompt")]
+ AgentPrompt,
+}
+
+/// Completed result discriminator
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandCompletedResultKind {
+ #[serde(rename = "completed")]
+ Completed,
+}
+
+/// Text result discriminator
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum SlashCommandTextResultKind {
+ #[serde(rename = "text")]
+ Text,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum SlashCommandInvocationResult {
+ Text(SlashCommandTextResult),
+ AgentPrompt(SlashCommandAgentPromptResult),
+ Completed(SlashCommandCompletedResult),
+}
+
/// How the agent is currently being managed by the runtime
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum TaskAgentInfoExecutionMode {
diff --git a/rust/src/generated/rpc.rs b/rust/src/generated/rpc.rs
index 5a0b48434..cb1393483 100644
--- a/rust/src/generated/rpc.rs
+++ b/rust/src/generated/rpc.rs
@@ -105,6 +105,19 @@ impl<'a> ClientRpcAccount<'a> {
.await?;
Ok(serde_json::from_value(_value)?)
}
+
+ /// Wire method: `account.getQuota`.
+ pub async fn get_quota_with_params(
+ &self,
+ params: AccountGetQuotaRequest,
+ ) -> Result {
+ let wire_params = serde_json::to_value(params)?;
+ let _value = self
+ .client
+ .call(rpc_methods::ACCOUNT_GETQUOTA, Some(wire_params))
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
}
/// `mcp.*` RPCs.
@@ -216,6 +229,16 @@ impl<'a> ClientRpcModels<'a> {
.await?;
Ok(serde_json::from_value(_value)?)
}
+
+ /// Wire method: `models.list`.
+ pub async fn list_with_params(&self, params: ModelsListRequest) -> Result {
+ let wire_params = serde_json::to_value(params)?;
+ let _value = self
+ .client
+ .call(rpc_methods::MODELS_LIST, Some(wire_params))
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
}
/// `sessionFs.*` RPCs.
@@ -647,6 +670,47 @@ pub struct SessionRpcCommands<'a> {
}
impl<'a> SessionRpcCommands<'a> {
+ /// Wire method: `session.commands.list`.
+ pub async fn list(&self) -> Result {
+ let wire_params = serde_json::json!({ "sessionId": self.session.id() });
+ let _value = self
+ .session
+ .client()
+ .call(rpc_methods::SESSION_COMMANDS_LIST, Some(wire_params))
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
+
+ /// Wire method: `session.commands.list`.
+ pub async fn list_with_params(
+ &self,
+ params: CommandsListRequest,
+ ) -> Result {
+ let mut wire_params = serde_json::to_value(params)?;
+ wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());
+ let _value = self
+ .session
+ .client()
+ .call(rpc_methods::SESSION_COMMANDS_LIST, Some(wire_params))
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
+
+ /// Wire method: `session.commands.invoke`.
+ pub async fn invoke(
+ &self,
+ params: CommandsInvokeRequest,
+ ) -> Result {
+ let mut wire_params = serde_json::to_value(params)?;
+ wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());
+ let _value = self
+ .session
+ .client()
+ .call(rpc_methods::SESSION_COMMANDS_INVOKE, Some(wire_params))
+ .await?;
+ Ok(serde_json::from_value(_value)?)
+ }
+
/// Wire method: `session.commands.handlePendingCommand`.
pub async fn handle_pending_command(
&self,
@@ -1369,14 +1433,14 @@ impl<'a> SessionRpcSkills<'a> {
/// SDK and CLI versions if your code depends on it.
///
///
- pub async fn reload(&self) -> Result<(), Error> {
+ pub async fn reload(&self) -> Result {
let wire_params = serde_json::json!({ "sessionId": self.session.id() });
let _value = self
.session
.client()
.call(rpc_methods::SESSION_SKILLS_RELOAD, Some(wire_params))
.await?;
- Ok(())
+ Ok(serde_json::from_value(_value)?)
}
}
diff --git a/rust/src/generated/session_events.rs b/rust/src/generated/session_events.rs
index 85c523940..37ac4fbe1 100644
--- a/rust/src/generated/session_events.rs
+++ b/rust/src/generated/session_events.rs
@@ -988,6 +988,9 @@ pub struct UserMessageData {
/// CAPI interaction ID for correlating this user message with its turn
#[serde(skip_serializing_if = "Option::is_none")]
pub interaction_id: Option,
+ /// True when this user message was auto-injected by autopilot's continuation loop rather than typed by the user; used to distinguish autopilot-driven turns in telemetry.
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub is_autopilot_continuation: Option,
/// Path-backed native document attachments that stayed on the tagged_files path flow because native upload would exceed the request size limit
#[serde(default)]
pub native_document_path_fallback_paths: Vec,
@@ -1221,6 +1224,9 @@ pub struct AssistantUsageData {
/// Completion ID from the model provider (e.g., chatcmpl-abc123)
#[serde(skip_serializing_if = "Option::is_none")]
pub api_call_id: Option,
+ /// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub api_endpoint: Option,
/// Number of tokens read from prompt cache
#[serde(skip_serializing_if = "Option::is_none")]
pub cache_read_tokens: Option,
@@ -2739,6 +2745,22 @@ pub enum AssistantMessageToolRequestType {
Unknown,
}
+/// API endpoint used for this model call, matching CAPI supported_endpoints vocabulary
+#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
+pub enum AssistantUsageApiEndpoint {
+ #[serde(rename = "/chat/completions")]
+ ChatCompletions,
+ #[serde(rename = "/v1/messages")]
+ V1Messages,
+ #[serde(rename = "/responses")]
+ Responses,
+ #[serde(rename = "ws:/responses")]
+ WsResponses,
+ /// Unknown variant for forward compatibility.
+ #[serde(other)]
+ Unknown,
+}
+
/// Where the failed model call originated
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ModelCallFailureSource {
diff --git a/rust/src/handler.rs b/rust/src/handler.rs
index 4520dd5e3..d3eaa9e92 100644
--- a/rust/src/handler.rs
+++ b/rust/src/handler.rs
@@ -147,9 +147,10 @@ pub enum PermissionResult {
/// `{ "kind": "user-not-available" }`.
UserNotAvailable,
/// The handler has no result to provide and the CLI should fall back
- /// to its default policy. Sent as `{ "kind": "no-result" }`. Distinct
- /// from [`Deferred`](Self::Deferred), which suppresses the reply
- /// entirely so the handler can resolve later out-of-band.
+ /// to another permission responder or its default policy. On the
+ /// notification path, the SDK will not send a pending permission response.
+ /// Distinct from [`Deferred`](Self::Deferred), where the handler takes
+ /// responsibility for resolving the request later out-of-band.
NoResult,
}
diff --git a/rust/src/session.rs b/rust/src/session.rs
index 2cdb257eb..9485fe219 100644
--- a/rust/src/session.rs
+++ b/rust/src/session.rs
@@ -1129,12 +1129,12 @@ fn permission_request_response(response: &HandlerResponse) -> PermissionDecision
/// Map a handler response into the `result` payload for the notification
/// path (`session.permissions.handlePendingPermissionRequest`).
///
-/// Returns `None` when the SDK must not respond — currently only the
-/// [`PermissionResult::Deferred`] case, where the handler takes over
-/// responsibility for the round-trip itself.
+/// Returns `None` when the SDK must not respond.
fn notification_permission_payload(response: &HandlerResponse) -> Option {
match response {
- HandlerResponse::Permission(PermissionResult::Deferred) => None,
+ HandlerResponse::Permission(PermissionResult::Deferred | PermissionResult::NoResult) => {
+ None
+ }
HandlerResponse::Permission(PermissionResult::Custom(value)) => Some(value.clone()),
_ => Some(serde_json::json!({
"kind": pending_permission_result_kind(response),
@@ -2116,14 +2116,20 @@ mod tests {
}
#[test]
- fn notification_payload_handles_deferred_and_custom() {
- // Deferred → no payload, SDK must not respond.
+ fn notification_payload_handles_non_responses_and_custom() {
+ // Deferred/NoResult -> no payload, SDK must not respond.
assert!(
notification_permission_payload(&HandlerResponse::Permission(
PermissionResult::Deferred,
))
.is_none()
);
+ assert!(
+ notification_permission_payload(&HandlerResponse::Permission(
+ PermissionResult::NoResult,
+ ))
+ .is_none()
+ );
// Custom → handler-supplied value passed through verbatim.
let custom = json!({
diff --git a/rust/tests/e2e/rpc_session_state.rs b/rust/tests/e2e/rpc_session_state.rs
index 8a8ae5c18..8d91a7731 100644
--- a/rust/tests/e2e/rpc_session_state.rs
+++ b/rust/tests/e2e/rpc_session_state.rs
@@ -641,6 +641,7 @@ async fn should_fork_session_with_persisted_messages() {
.rpc()
.sessions()
.fork(SessionsForkRequest {
+ name: None,
session_id: session.id().clone(),
to_event_id: None,
})
@@ -706,6 +707,7 @@ async fn should_handle_forking_session_without_persisted_events() {
.rpc()
.sessions()
.fork(SessionsForkRequest {
+ name: None,
session_id: session.id().clone(),
to_event_id: None,
})
@@ -799,6 +801,7 @@ async fn should_fork_session_to_event_id_excluding_boundary_event() {
.rpc()
.sessions()
.fork(SessionsForkRequest {
+ name: None,
session_id: session.id().clone(),
to_event_id: Some(boundary_id.clone()),
})
@@ -858,6 +861,7 @@ async fn should_report_error_when_forking_session_to_unknown_event_id() {
.rpc()
.sessions()
.fork(SessionsForkRequest {
+ name: None,
session_id: session.id().clone(),
to_event_id: Some(bogus_event_id.to_string()),
})
diff --git a/scripts/codegen/csharp.ts b/scripts/codegen/csharp.ts
index a1401e1a5..f66043170 100644
--- a/scripts/codegen/csharp.ts
+++ b/scripts/codegen/csharp.ts
@@ -1266,19 +1266,14 @@ function emitRpcClass(
return lines.join("\n");
}
-/**
- * Emit the type for a non-object RPC result schema (e.g., a bare enum).
- * Returns the C# type name to use in method signatures. For enums, ensures the enum
- * is created via getOrCreateEnum. For other primitives, returns the mapped C# type.
- */
-function emitNonObjectResultType(typeName: string, schema: JSONSchema7, classes: string[]): string {
- if (schema.enum && Array.isArray(schema.enum)) {
- const enumName = getOrCreateEnum("", typeName, schema.enum as string[], rpcEnumOutput, schema.description, typeName, isSchemaDeprecated(schema), isSchemaExperimental(schema));
- emittedRpcEnumResultTypes.add(enumName);
- return enumName;
+function emitRpcResultType(typeName: string, schema: JSONSchema7, visibility: "public" | "internal", classes: string[]): string {
+ if (isObjectSchema(schema)) {
+ const resultClass = emitRpcClass(typeName, schema, visibility, classes);
+ if (resultClass) classes.push(resultClass);
+ return typeName;
}
- // For other non-object types, use the basic type mapping
- return schemaTypeToCSharp(schema, true, rpcKnownTypes);
+
+ return resolveRpcType(schema, true, typeName, "", classes);
}
/**
@@ -1399,11 +1394,8 @@ function emitServerInstanceMethod(
if (!isVoidSchema(resultSchema) && method.stability === "experimental") {
experimentalRpcTypes.add(resultClassName);
}
- if (isObjectSchema(resultSchema)) {
- const resultClass = emitRpcClass(resultClassName, resultSchema!, methodVisibility, classes);
- if (resultClass) classes.push(resultClass);
- } else if (!isVoidSchema(resultSchema)) {
- resultClassName = emitNonObjectResultType(resultClassName, resultSchema!, classes);
+ if (!isVoidSchema(resultSchema)) {
+ resultClassName = emitRpcResultType(resultClassName, resultSchema!, methodVisibility, classes);
}
const effectiveParams = resolveMethodParamsSchema(method);
@@ -1507,16 +1499,17 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
if (!isVoidSchema(resultSchema) && method.stability === "experimental") {
experimentalRpcTypes.add(resultClassName);
}
- if (isObjectSchema(resultSchema)) {
- const resultClass = emitRpcClass(resultClassName, resultSchema!, methodVisibility, classes);
- if (resultClass) classes.push(resultClass);
- } else if (!isVoidSchema(resultSchema)) {
- resultClassName = emitNonObjectResultType(resultClassName, resultSchema!, classes);
+ if (!isVoidSchema(resultSchema)) {
+ resultClassName = emitRpcResultType(resultClassName, resultSchema!, methodVisibility, classes);
}
const effectiveParams = resolveMethodParamsSchema(method);
const paramEntries = (effectiveParams?.properties ? Object.entries(effectiveParams.properties) : []).filter(([k]) => k !== "sessionId");
const requiredSet = new Set(effectiveParams?.required || []);
+ const useRequestParameter =
+ paramEntries.length > 0 &&
+ !!getNullableInner(method.params) &&
+ paramEntries.every(([name]) => !requiredSet.has(name));
// Sort so required params come before optional (C# requires defaults at end)
paramEntries.sort((a, b) => {
@@ -1526,12 +1519,28 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
});
const requestClassName = paramsTypeName(method);
+ const wireRequestClassName = useRequestParameter ? `${requestClassName}WithSession` : requestClassName;
if (method.stability === "experimental") {
experimentalRpcTypes.add(requestClassName);
+ if (useRequestParameter) {
+ experimentalRpcTypes.add(wireRequestClassName);
+ }
}
if (effectiveParams?.properties && Object.keys(effectiveParams.properties).length > 0) {
- const reqClass = emitRpcClass(requestClassName, effectiveParams, "internal", classes);
- if (reqClass) classes.push(reqClass);
+ if (useRequestParameter) {
+ const publicParams: JSONSchema7 = {
+ ...effectiveParams,
+ properties: Object.fromEntries(paramEntries),
+ required: effectiveParams.required?.filter((name) => name !== "sessionId"),
+ };
+ const publicReqClass = emitRpcClass(requestClassName, publicParams, methodVisibility, classes);
+ if (publicReqClass) classes.push(publicReqClass);
+ const wireReqClass = emitRpcClass(wireRequestClassName, effectiveParams, "internal", classes);
+ if (wireReqClass) classes.push(wireReqClass);
+ } else {
+ const reqClass = emitRpcClass(requestClassName, effectiveParams, "internal", classes);
+ if (reqClass) classes.push(reqClass);
+ }
}
lines.push("", `${indent}/// Calls "${method.rpcMethod}".`);
@@ -1544,22 +1553,30 @@ function emitSessionMethod(key: string, method: RpcMethod, lines: string[], clas
const sigParams: string[] = [];
const bodyAssignments = [`SessionId = _sessionId`];
- for (const [pName, pSchema] of paramEntries) {
- if (typeof pSchema !== "object") continue;
- const isReq = requiredSet.has(pName);
- const csType = resolveRpcType(pSchema as JSONSchema7, isReq, requestClassName, toPascalCase(pName), classes);
- sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`);
- bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`);
+ if (useRequestParameter) {
+ sigParams.push(`${requestClassName}? request = null`);
+ for (const [pName] of paramEntries) {
+ bodyAssignments.push(`${toPascalCase(pName)} = request?.${toPascalCase(pName)}`);
+ }
+ } else {
+ for (const [pName, pSchema] of paramEntries) {
+ if (typeof pSchema !== "object") continue;
+ const isReq = requiredSet.has(pName);
+ const csType = resolveRpcType(pSchema as JSONSchema7, isReq, requestClassName, toPascalCase(pName), classes);
+ sigParams.push(`${csType} ${pName}${isReq ? "" : " = null"}`);
+ bodyAssignments.push(`${toPascalCase(pName)} = ${pName}`);
+ }
}
sigParams.push("CancellationToken cancellationToken = default");
const taskType = !isVoidSchema(resultSchema) ? `Task<${resultClassName}>` : "Task";
+ const localRequestName = useRequestParameter ? "rpcRequest" : "request";
lines.push(`${indent}${methodVisibility} async ${taskType} ${methodName}Async(${sigParams.join(", ")})`);
- lines.push(`${indent}{`, `${indent} var request = new ${requestClassName} { ${bodyAssignments.join(", ")} };`);
+ lines.push(`${indent}{`, `${indent} var ${localRequestName} = new ${wireRequestClassName} { ${bodyAssignments.join(", ")} };`);
if (!isVoidSchema(resultSchema)) {
- lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`);
+ lines.push(`${indent} return await CopilotClient.InvokeRpcAsync<${resultClassName}>(_rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`, `${indent}}`);
} else {
- lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [request], cancellationToken);`, `${indent}}`);
+ lines.push(`${indent} await CopilotClient.InvokeRpcAsync(_rpc, "${method.rpcMethod}", [${localRequestName}], cancellationToken);`, `${indent}}`);
}
}
@@ -1634,12 +1651,7 @@ function emitClientSessionApiRegistration(clientSchema: Record,
for (const method of methods) {
const resultSchema = getMethodResultSchema(method);
if (!isVoidSchema(resultSchema)) {
- if (isObjectSchema(resultSchema)) {
- const resultClass = emitRpcClass(resultTypeName(method), resultSchema!, "public", classes);
- if (resultClass) classes.push(resultClass);
- } else {
- emitNonObjectResultType(resultTypeName(method), resultSchema!, classes);
- }
+ emitRpcResultType(resultTypeName(method), resultSchema!, "public", classes);
}
const effectiveParams = resolveMethodParamsSchema(method);
diff --git a/scripts/codegen/go.ts b/scripts/codegen/go.ts
index b1a8fb080..0f251e626 100644
--- a/scripts/codegen/go.ts
+++ b/scripts/codegen/go.ts
@@ -2543,6 +2543,7 @@ function emitGoRpcDefinition(definitionName: string, schema: JSONSchema7, ctx: G
interface GoGeneratedTypeCode {
typeCode: string;
encodingCode: string;
+ discriminatedUnions: Map;
}
function stripTrailingGoWhitespace(code: string): string {
@@ -2637,6 +2638,7 @@ function generateGoRpcTypeCode(definitions: Record, definit
return {
typeCode: joinGoCode(lines),
encodingCode: goEncodingBlocksCode(ctx.encoding),
+ discriminatedUnions: new Map(ctx.discriminatedUnions),
};
}
@@ -2965,6 +2967,7 @@ function generateGoSessionEventsCode(schema: JSONSchema7): GoGeneratedTypeCode {
return {
typeCode: joinGoCode(out),
encodingCode: joinGoCode(encodingOut),
+ discriminatedUnions: new Map(ctx.discriminatedUnions),
};
}
@@ -3168,21 +3171,21 @@ async function generateRpc(schemaPath?: string): Promise {
// Emit ServerRpc
if (schema.server) {
const publicNode = filterNodeByVisibility(schema.server, "public");
- if (publicNode) emitRpcWrapper(lines, publicNode, false, resolveType, fields, "");
+ if (publicNode) emitRpcWrapper(lines, publicNode, false, resolveType, fields, generatedRpcCode.discriminatedUnions, "");
const internalNode = filterNodeByVisibility(schema.server, "internal");
- if (internalNode) emitRpcWrapper(lines, internalNode, false, resolveType, fields, "Internal");
+ if (internalNode) emitRpcWrapper(lines, internalNode, false, resolveType, fields, generatedRpcCode.discriminatedUnions, "Internal");
}
// Emit SessionRpc
if (schema.session) {
const publicNode = filterNodeByVisibility(schema.session, "public");
- if (publicNode) emitRpcWrapper(lines, publicNode, true, resolveType, fields, "");
+ if (publicNode) emitRpcWrapper(lines, publicNode, true, resolveType, fields, generatedRpcCode.discriminatedUnions, "");
const internalNode = filterNodeByVisibility(schema.session, "internal");
- if (internalNode) emitRpcWrapper(lines, internalNode, true, resolveType, fields, "Internal");
+ if (internalNode) emitRpcWrapper(lines, internalNode, true, resolveType, fields, generatedRpcCode.discriminatedUnions, "Internal");
}
if (schema.clientSession) {
- emitClientSessionApiRegistration(lines, schema.clientSession, resolveType);
+ emitClientSessionApiRegistration(lines, schema.clientSession, resolveType, generatedRpcCode.discriminatedUnions);
}
const outPath = await writeGeneratedFile("go/rpc/zrpc.go", wrapGeneratedGoComments(lines.join("\n")));
@@ -3204,6 +3207,7 @@ function emitApiGroup(
serviceName: string,
resolveType: (name: string) => string,
fields: Map>,
+ unionInfos: Map,
groupExperimental: boolean,
groupDeprecated: boolean = false
): void {
@@ -3221,14 +3225,14 @@ function emitApiGroup(
for (const [key, value] of methods) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, apiName, key, value, isSession, resolveType, fields, groupExperimental, false, groupDeprecated);
+ emitMethod(lines, apiName, key, value, isSession, resolveType, fields, unionInfos, groupExperimental, false, groupDeprecated);
}
for (const [subGroupName, subGroupNode] of subGroups) {
const subApiName = apiName.replace(/Api$/, "") + toPascalCase(subGroupName) + "Api";
const subGroupExperimental = isNodeFullyExperimental(subGroupNode as Record);
const subGroupDeprecated = isNodeFullyDeprecated(subGroupNode as Record);
- emitApiGroup(lines, subApiName, subGroupNode as Record, isSession, serviceName, resolveType, fields, subGroupExperimental, subGroupDeprecated);
+ emitApiGroup(lines, subApiName, subGroupNode as Record, isSession, serviceName, resolveType, fields, unionInfos, subGroupExperimental, subGroupDeprecated);
if (subGroupExperimental) {
pushGoExperimentalSubApiComment(lines, toPascalCase(subGroupName));
@@ -3240,7 +3244,7 @@ function emitApiGroup(
}
}
-function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string, fields: Map>, classPrefix: string = ""): void {
+function emitRpcWrapper(lines: string[], node: Record, isSession: boolean, resolveType: (name: string) => string, fields: Map>, unionInfos: Map, classPrefix: string = ""): void {
const groups = sortByPascalName(Object.entries(node).filter(([, v]) => typeof v === "object" && v !== null && !isRpcMethod(v)));
const topLevelMethods = sortByPascalName(Object.entries(node).filter(([, v]) => isRpcMethod(v)));
@@ -3265,7 +3269,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
const apiName = prefix + toPascalCase(groupName) + apiSuffix;
const groupExperimental = isNodeFullyExperimental(groupNode as Record);
const groupDeprecated = isNodeFullyDeprecated(groupNode as Record);
- emitApiGroup(lines, apiName, groupNode as Record, isSession, serviceName, resolveType, fields, groupExperimental, groupDeprecated);
+ emitApiGroup(lines, apiName, groupNode as Record, isSession, serviceName, resolveType, fields, unionInfos, groupExperimental, groupDeprecated);
}
// Compute field name lengths for gofmt-compatible column alignment
@@ -3295,7 +3299,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
// Top-level methods on the wrapper use the common service fields
for (const [key, value] of topLevelMethods) {
if (!isRpcMethod(value)) continue;
- emitMethod(lines, wrapperName, key, value, isSession, resolveType, fields, false, true);
+ emitMethod(lines, wrapperName, key, value, isSession, resolveType, fields, unionInfos, false, true);
}
// Constructor
@@ -3316,13 +3320,15 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
lines.push(``);
}
-function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fields: Map>, groupExperimental = false, isWrapper = false, groupDeprecated = false): void {
+function emitMethod(lines: string[], receiver: string, name: string, method: RpcMethod, isSession: boolean, resolveType: (name: string) => string, fields: Map>, unionInfos: Map, groupExperimental = false, isWrapper = false, groupDeprecated = false): void {
const methodName = toPascalCase(name);
const resultSchema = getMethodResultSchema(method);
const nullableInner = resultSchema ? getNullableInner(resultSchema) : undefined;
const resultType = nullableInner
? resolveType(goNullableResultTypeName(method, nullableInner))
: resolveType(goResultTypeName(method));
+ const resultUnion = unionInfos.get(resultType);
+ const returnType = resultUnion ? resultType : `*${resultType}`;
const effectiveParams = getMethodParamsSchema(method);
const paramProps = effectiveParams?.properties || {};
@@ -3332,6 +3338,8 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
.sort((left, right) => compareGoFieldNames(toGoFieldName(left), toGoFieldName(right)));
const hasParams = isSession ? nonSessionParams.length > 0 : hasSchemaPayload(effectiveParams);
const paramsType = hasParams ? resolveType(goParamsTypeName(method)) : "";
+ const hasRequiredNonSessionParams = nonSessionParams.some((name) => requiredParams.has(name));
+ const paramsAreOptional = hasParams && !!method.params && !!getNullableInner(method.params) && !hasRequiredNonSessionParams;
// For wrapper-level methods, access fields through a.common; for service type aliases, use a directly
const clientRef = isWrapper ? "a.common.client" : "a.client";
@@ -3347,15 +3355,22 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
pushGoComment(lines, `Internal: ${methodName} is part of the SDK's internal handshake/plumbing; external callers should not use it.`);
}
const sig = hasParams
- ? `func (a *${receiver}) ${methodName}(ctx context.Context, params *${paramsType}) (*${resultType}, error)`
- : `func (a *${receiver}) ${methodName}(ctx context.Context) (*${resultType}, error)`;
+ ? `func (a *${receiver}) ${methodName}(ctx context.Context, params ${paramsAreOptional ? "..." : ""}*${paramsType}) (${returnType}, error)`
+ : `func (a *${receiver}) ${methodName}(ctx context.Context) (${returnType}, error)`;
lines.push(sig + ` {`);
+ const paramsRef = paramsAreOptional ? "requestParams" : "params";
+ if (paramsAreOptional) {
+ lines.push(`\tvar requestParams *${paramsType}`);
+ lines.push(`\tif len(params) > 0 {`);
+ lines.push(`\t\trequestParams = params[0]`);
+ lines.push(`\t}`);
+ }
if (isSession) {
lines.push(`\treq := map[string]any{"sessionId": ${sessionIDRef}}`);
if (hasParams) {
- lines.push(`\tif params != nil {`);
+ lines.push(`\tif ${paramsRef} != nil {`);
for (const pName of nonSessionParams) {
const field = fields.get(paramsType)?.get(pName);
const goField = field?.name ?? toGoFieldName(pName);
@@ -3364,30 +3379,38 @@ function emitMethod(lines: string[], receiver: string, name: string, method: Rpc
if (isOptional) {
// Optional fields are usually pointers; generated union interfaces, slices,
// and maps are nilable values and should be passed through directly.
- lines.push(`\t\tif params.${goField} != nil {`);
- const valueExpr = goOptionalFieldNeedsDereference(goType) ? `*params.${goField}` : `params.${goField}`;
+ lines.push(`\t\tif ${paramsRef}.${goField} != nil {`);
+ const valueExpr = goOptionalFieldNeedsDereference(goType) ? `*${paramsRef}.${goField}` : `${paramsRef}.${goField}`;
lines.push(`\t\t\treq["${pName}"] = ${valueExpr}`);
lines.push(`\t\t}`);
} else {
- lines.push(`\t\treq["${pName}"] = params.${goField}`);
+ lines.push(`\t\treq["${pName}"] = ${paramsRef}.${goField}`);
}
}
lines.push(`\t}`);
}
lines.push(`\traw, err := ${clientRef}.Request("${method.rpcMethod}", req)`);
} else {
- const arg = hasParams ? "params" : "nil";
+ const arg = hasParams ? paramsRef : "nil";
lines.push(`\traw, err := ${clientRef}.Request("${method.rpcMethod}", ${arg})`);
}
lines.push(`\tif err != nil {`);
lines.push(`\t\treturn nil, err`);
lines.push(`\t}`);
- lines.push(`\tvar result ${resultType}`);
- lines.push(`\tif err := json.Unmarshal(raw, &result); err != nil {`);
- lines.push(`\t\treturn nil, err`);
- lines.push(`\t}`);
- lines.push(`\treturn &result, nil`);
+ if (resultUnion) {
+ lines.push(`\tresult, err := ${resultUnion.unmarshalFuncName}(raw)`);
+ lines.push(`\tif err != nil {`);
+ lines.push(`\t\treturn nil, err`);
+ lines.push(`\t}`);
+ lines.push(`\treturn result, nil`);
+ } else {
+ lines.push(`\tvar result ${resultType}`);
+ lines.push(`\tif err := json.Unmarshal(raw, &result); err != nil {`);
+ lines.push(`\t\treturn nil, err`);
+ lines.push(`\t}`);
+ lines.push(`\treturn &result, nil`);
+ }
lines.push(`}`);
lines.push(``);
}
@@ -3420,7 +3443,7 @@ function clientHandlerMethodName(rpcMethod: string): string {
return toPascalCase(rpcMethod.split(".").at(-1)!);
}
-function emitClientSessionApiRegistration(lines: string[], clientSchema: Record, resolveType: (name: string) => string): void {
+function emitClientSessionApiRegistration(lines: string[], clientSchema: Record, resolveType: (name: string) => string, unionInfos: Map): void {
const groups = collectClientGroups(clientSchema);
for (const { groupName, groupNode, methods } of groups) {
@@ -3447,7 +3470,8 @@ function emitClientSessionApiRegistration(lines: string[], clientSchema: Record<
const resultType = nullableInner
? resolveType(goNullableResultTypeName(method, nullableInner))
: resolveType(goResultTypeName(method));
- lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (*${resultType}, error)`);
+ const returnType = unionInfos.has(resultType) ? resultType : `*${resultType}`;
+ lines.push(`\t${clientHandlerMethodName(method.rpcMethod)}(request *${paramsType}) (${returnType}, error)`);
}
lines.push(`}`);
lines.push(``);
diff --git a/scripts/codegen/python.ts b/scripts/codegen/python.ts
index 30aff56bd..6c0a4b572 100644
--- a/scripts/codegen/python.ts
+++ b/scripts/codegen/python.ts
@@ -445,6 +445,33 @@ function getMethodResultSchema(method: RpcMethod): JSONSchema7 | undefined {
return resolveSchema(method.result, rpcDefinitions) ?? method.result ?? undefined;
}
+function isPythonObjectResultSchema(schema: JSONSchema7 | undefined): boolean {
+ if (!schema) return false;
+ if (isObjectSchema(schema)) return true;
+
+ const variants = schema.anyOf ?? schema.oneOf;
+ if (!Array.isArray(variants)) return false;
+
+ const nonNullVariants = variants
+ .filter((variant): variant is JSONSchema7 => typeof variant === "object" && variant !== null)
+ .map((variant) => resolveObjectSchema(variant, rpcDefinitions) ?? resolveSchema(variant, rpcDefinitions) ?? variant)
+ .filter(
+ (variant) =>
+ variant.type !== "null" &&
+ !(
+ typeof variant.not === "object" &&
+ variant.not !== null &&
+ Object.keys(variant.not).length === 0
+ )
+ );
+
+ if (nonNullVariants.length === 1) {
+ return isPythonObjectResultSchema(nonNullVariants[0]);
+ }
+
+ return nonNullVariants.length > 1 && findPyDiscriminator(nonNullVariants) !== null;
+}
+
function getMethodParamsSchema(method: RpcMethod): JSONSchema7 | undefined {
return (
resolveObjectSchema(method.params, rpcDefinitions) ??
@@ -1849,7 +1876,32 @@ async function generateRpc(schemaPath?: string): Promise {
while ((cm = classRe.exec(typesCode)) !== null) {
actualTypeNames.set(cm[1].toLowerCase(), cm[1]);
}
- const resolveType = (name: string): string => actualTypeNames.get(name.toLowerCase()) ?? name;
+
+ // quicktype can also choose a shorter generated class name for a titled schema
+ // definition. Its root RPC dataclass still records the definition field and
+ // generated class mapping, so use that as an alias table for RPC wrappers.
+ const definitionAliases = new Map();
+ const publicTypeAliases = new Map();
+ const rootFields = typesCode.match(/^class RPC:\n([\s\S]*?)\n @staticmethod/m)?.[1] ?? "";
+ const rootFieldTypes = new Map();
+ for (const line of rootFields.split(/\r?\n/)) {
+ const match = line.match(/^ ([A-Za-z_]\w*): ([A-Za-z_]\w*)\b/);
+ if (match) {
+ rootFieldTypes.set(match[1], match[2]);
+ }
+ }
+ for (const defName of Object.keys(allDefinitions)) {
+ const actualName = rootFieldTypes.get(toSnakeCase(defName));
+ if (actualName) {
+ definitionAliases.set(defName.toLowerCase(), actualName);
+ if (actualName !== defName && !actualTypeNames.has(defName.toLowerCase()) && /^[A-Za-z_]\w*$/.test(defName)) {
+ publicTypeAliases.set(defName, actualName);
+ }
+ }
+ }
+
+ const resolveType = (name: string): string =>
+ actualTypeNames.get(name.toLowerCase()) ?? definitionAliases.get(name.toLowerCase()) ?? name;
const lines: string[] = [];
lines.push(`"""
@@ -1876,6 +1928,14 @@ EnumT = TypeVar("EnumT", bound=Enum)
`);
lines.push(typesCode);
+ if (publicTypeAliases.size > 0) {
+ lines.push("");
+ for (const [aliasName, targetName] of [...publicTypeAliases.entries()].sort(([left], [right]) =>
+ left.localeCompare(right),
+ )) {
+ lines.push(`${aliasName} = ${targetName}`);
+ }
+ }
lines.push(`
def _timeout_kwargs(timeout: float | None) -> dict:
"""Build keyword arguments for optional timeout forwarding."""
@@ -2024,7 +2084,7 @@ function emitRpcWrapper(lines: string[], node: Record, isSessio
} else {
lines.push(`class ${wrapperName}:`);
lines.push(classPrefix === "_Internal"
- ? ` """Internal SDK server-scoped RPC methods (handshake helpers etc.). Not part of the public API."""`
+ ? ` """Internal SDK server-scoped RPC methods. Not part of the public API."""`
: ` """Typed server-scoped RPC methods."""`);
lines.push(` def __init__(self, client: "JsonRpcClient"):`);
lines.push(` self._client = client`);
@@ -2049,7 +2109,7 @@ function emitMethod(lines: string[], name: string, method: RpcMethod, isSession:
const effectiveResultSchema = nullableInner ?? resultSchema;
const hasResult = !isVoidSchema(resultSchema) && !nullableInner;
const hasNullableResult = !!nullableInner;
- const resultIsObject = isObjectSchema(effectiveResultSchema);
+ const resultIsObject = isPythonObjectResultSchema(effectiveResultSchema);
let resultType: string;
if (hasNullableResult) {
diff --git a/scripts/codegen/rust.ts b/scripts/codegen/rust.ts
index b21a889e4..15c8006ee 100644
--- a/scripts/codegen/rust.ts
+++ b/scripts/codegen/rust.ts
@@ -24,6 +24,7 @@ import {
collectDefinitionCollections,
collectDefinitions,
getApiSchemaPath,
+ getNullableInner,
getRpcSchemaTypeName,
getSessionEventsSchemaPath,
isObjectSchema,
@@ -1116,11 +1117,12 @@ function getMethodParamsInfo(
method: RpcMethod,
defCollections: DefinitionCollections,
isSession: boolean,
-): { hasParams: boolean; typeName: string | null } {
- if (!method.params) return { hasParams: false, typeName: null };
+): { hasParams: boolean; optional: boolean; typeName: string | null } {
+ if (!method.params) return { hasParams: false, optional: false, typeName: null };
const inline = method.params as JSONSchema7 & { $ref?: string };
- const resolved = resolveSchema(inline, defCollections);
- if (!resolved) return { hasParams: false, typeName: null };
+ const resolved = resolveObjectSchema(inline, defCollections) ??
+ resolveSchema(inline, defCollections);
+ if (!resolved) return { hasParams: false, optional: false, typeName: null };
let typeName: string | null = null;
if (typeof inline.$ref === "string") {
@@ -1135,9 +1137,12 @@ function getMethodParamsInfo(
const props = isSession
? allProps.filter((p) => p !== "sessionId")
: allProps;
- if (props.length === 0) return { hasParams: false, typeName: null };
- if (!typeName) return { hasParams: false, typeName: null };
- return { hasParams: true, typeName };
+ if (props.length === 0) return { hasParams: false, optional: false, typeName: null };
+ if (!typeName) return { hasParams: false, optional: false, typeName: null };
+ const required = new Set(resolved.required || []);
+ const hasRequiredParams = props.some((p) => required.has(p));
+ const optional = !!getNullableInner(inline) && !hasRequiredParams;
+ return { hasParams: true, optional, typeName };
}
function rpcMethodConstName(method: RpcMethod): string {
@@ -1228,6 +1233,47 @@ function getResultTypeName(
return `${toPascalCase(method.rpcMethod)}Result`;
}
+function pushNamespaceMethodBody(
+ out: string[],
+ constName: string,
+ isSession: boolean,
+ hasParams: boolean,
+ resultIsVoid: boolean,
+): void {
+ // Build the params Value sent over the wire.
+ if (isSession) {
+ if (hasParams) {
+ out.push(` let mut wire_params = serde_json::to_value(params)?;`);
+ out.push(
+ ` wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());`,
+ );
+ } else {
+ out.push(
+ ` let wire_params = serde_json::json!({ "sessionId": self.session.id() });`,
+ );
+ }
+ out.push(
+ ` let _value = self.session.client().call(rpc_methods::${constName}, Some(wire_params)).await?;`,
+ );
+ } else {
+ if (hasParams) {
+ out.push(` let wire_params = serde_json::to_value(params)?;`);
+ } else {
+ out.push(` let wire_params = serde_json::json!({});`);
+ }
+ out.push(
+ ` let _value = self.client.call(rpc_methods::${constName}, Some(wire_params)).await?;`,
+ );
+ }
+
+ if (resultIsVoid) {
+ out.push(` Ok(())`);
+ } else {
+ out.push(` Ok(serde_json::from_value(_value)?)`);
+ }
+ out.push(` }`);
+}
+
function emitNamespaceMethod(
out: string[],
method: RpcMethod,
@@ -1282,42 +1328,25 @@ function emitNamespaceMethod(
const paramArg = hasParams ? `, params: ${paramsTypeName}` : "";
out.push(...docs);
- out.push(
- ` pub async fn ${fnName}(&self${paramArg}) -> Result<${returnType}, Error> {`,
- );
-
- // Build the params Value sent over the wire.
- if (isSession) {
- if (hasParams) {
- out.push(` let mut wire_params = serde_json::to_value(params)?;`);
- out.push(
- ` wire_params["sessionId"] = serde_json::Value::String(self.session.id().to_string());`,
- );
- } else {
- out.push(
- ` let wire_params = serde_json::json!({ "sessionId": self.session.id() });`,
- );
- }
+ if (hasParams && paramsInfo.optional) {
out.push(
- ` let _value = self.session.client().call(rpc_methods::${constName}, Some(wire_params)).await?;`,
+ ` pub async fn ${fnName}(&self) -> Result<${returnType}, Error> {`,
);
- } else {
- if (hasParams) {
- out.push(` let wire_params = serde_json::to_value(params)?;`);
- } else {
- out.push(` let wire_params = serde_json::json!({});`);
- }
+ pushNamespaceMethodBody(out, constName, isSession, false, resultIsVoid);
+ out.push("");
+ out.push(...docs);
out.push(
- ` let _value = self.client.call(rpc_methods::${constName}, Some(wire_params)).await?;`,
+ ` pub async fn ${fnName}_with_params(&self, params: ${paramsTypeName}) -> Result<${returnType}, Error> {`,
);
+ pushNamespaceMethodBody(out, constName, isSession, true, resultIsVoid);
+ out.push("");
+ return;
}
- if (resultIsVoid) {
- out.push(` Ok(())`);
- } else {
- out.push(` Ok(serde_json::from_value(_value)?)`);
- }
- out.push(` }`);
+ out.push(
+ ` pub async fn ${fnName}(&self${paramArg}) -> Result<${returnType}, Error> {`,
+ );
+ pushNamespaceMethodBody(out, constName, isSession, hasParams, resultIsVoid);
out.push("");
}
diff --git a/scripts/codegen/utils.ts b/scripts/codegen/utils.ts
index 85d7c1acf..84c2d4a03 100644
--- a/scripts/codegen/utils.ts
+++ b/scripts/codegen/utils.ts
@@ -502,54 +502,76 @@ export function resolveSchema(
return current;
}
+function hasObjectShape(schema: JSONSchema7): boolean {
+ return !!(schema.properties || schema.additionalProperties || schema.type === "object");
+}
+
+function isEmptyNotSchema(schema: JSONSchema7): boolean {
+ return !!schema.not && typeof schema.not === "object" && Object.keys(schema.not).length === 0;
+}
+
+function mergeObjectSchemas(schemas: JSONSchema7[]): JSONSchema7 | undefined {
+ const mergedProperties: Record = {};
+ const mergedRequired = new Set();
+ const merged: JSONSchema7 = {
+ type: "object",
+ };
+ let hasShape = false;
+
+ for (const objectSchema of schemas) {
+ if (!merged.title && objectSchema.title) {
+ merged.title = objectSchema.title;
+ }
+ if (!merged.description && objectSchema.description) {
+ merged.description = objectSchema.description;
+ }
+ if (objectSchema.properties) {
+ Object.assign(mergedProperties, objectSchema.properties);
+ hasShape = true;
+ }
+ if (objectSchema.required) {
+ for (const name of objectSchema.required) {
+ mergedRequired.add(name);
+ }
+ }
+ if (objectSchema.additionalProperties !== undefined) {
+ merged.additionalProperties = objectSchema.additionalProperties;
+ hasShape = true;
+ }
+ }
+
+ if (!hasShape) return undefined;
+ if (Object.keys(mergedProperties).length > 0) {
+ merged.properties = mergedProperties;
+ }
+ if (mergedRequired.size > 0) {
+ merged.required = [...mergedRequired];
+ }
+ return merged;
+}
+
export function resolveObjectSchema(
schema: JSONSchema7 | null | undefined,
definitions: DefinitionCollections | undefined
): JSONSchema7 | undefined {
const resolved = resolveSchema(schema, definitions) ?? schema ?? undefined;
if (!resolved) return undefined;
- if (resolved.properties || resolved.additionalProperties || resolved.type === "object") return resolved;
+ const resolvedHasObjectShape = hasObjectShape(resolved);
if (resolved.allOf) {
- const mergedProperties: Record = {};
- const mergedRequired = new Set();
- const merged: JSONSchema7 = {
- type: "object",
- description: resolved.description,
- };
- let hasObjectShape = false;
+ const objectSchemas: JSONSchema7[] = [];
+ if (resolvedHasObjectShape) {
+ objectSchemas.push(resolved);
+ }
for (const item of resolved.allOf) {
if (typeof item !== "object") continue;
const objectSchema = resolveObjectSchema(item as JSONSchema7, definitions);
if (!objectSchema) continue;
-
- if (objectSchema.properties) {
- Object.assign(mergedProperties, objectSchema.properties);
- hasObjectShape = true;
- }
- if (objectSchema.required) {
- for (const name of objectSchema.required) {
- mergedRequired.add(name);
- }
- }
- if (objectSchema.additionalProperties !== undefined) {
- merged.additionalProperties = objectSchema.additionalProperties;
- hasObjectShape = true;
- }
- if (!merged.description && objectSchema.description) {
- merged.description = objectSchema.description;
- }
+ objectSchemas.push(objectSchema);
}
- if (!hasObjectShape) return resolved;
- if (Object.keys(mergedProperties).length > 0) {
- merged.properties = mergedProperties;
- }
- if (mergedRequired.size > 0) {
- merged.required = [...mergedRequired];
- }
- return merged;
+ return mergeObjectSchemas(objectSchemas) ?? resolved;
}
const singleBranch = (resolved.anyOf ?? resolved.oneOf)
@@ -558,13 +580,20 @@ export function resolveObjectSchema(
const s = item as JSONSchema7;
// Filter out null types and `{ not: {} }` (Zod's representation of "nothing" in optional anyOf)
if (s.type === "null") return false;
- if (s.not && typeof s.not === "object" && Object.keys(s.not).length === 0) return false;
+ if (isEmptyNotSchema(s)) return false;
return true;
});
if (singleBranch && singleBranch.length === 1) {
- return resolveObjectSchema(singleBranch[0], definitions);
+ const objectSchema = resolveObjectSchema(singleBranch[0], definitions);
+ if (!objectSchema) return resolved;
+ if (resolvedHasObjectShape) {
+ return mergeObjectSchemas([resolved, objectSchema]) ?? objectSchema;
+ }
+ return objectSchema;
}
+ if (resolvedHasObjectShape) return resolved;
+
return resolved;
}
diff --git a/test/harness/package-lock.json b/test/harness/package-lock.json
index 017220925..cbda49ccd 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.46",
+ "@github/copilot": "^1.0.47",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"@types/node-forge": "^1.3.14",
@@ -464,27 +464,27 @@
}
},
"node_modules/@github/copilot": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.46.tgz",
- "integrity": "sha512-e3gxCj8DLGesTAZQ5+jCCbCxe3lMyjKfs5eLgER/SID8Rcb7YpgBXoUvOn3eXxLSsJEmJ3GagHaaHDkf3Zm+Ng==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.47.tgz",
+ "integrity": "sha512-U4WrajOOjjMVleqIRvRt+kDsjYQPLHxtJMMtdzW2N18dbRddlxqN+qo6ZOxOTy3tks2+YI+G89zyO1qpxpuWSg==",
"dev": true,
"license": "SEE LICENSE IN LICENSE.md",
"bin": {
"copilot": "npm-loader.js"
},
"optionalDependencies": {
- "@github/copilot-darwin-arm64": "1.0.46",
- "@github/copilot-darwin-x64": "1.0.46",
- "@github/copilot-linux-arm64": "1.0.46",
- "@github/copilot-linux-x64": "1.0.46",
- "@github/copilot-win32-arm64": "1.0.46",
- "@github/copilot-win32-x64": "1.0.46"
+ "@github/copilot-darwin-arm64": "1.0.47",
+ "@github/copilot-darwin-x64": "1.0.47",
+ "@github/copilot-linux-arm64": "1.0.47",
+ "@github/copilot-linux-x64": "1.0.47",
+ "@github/copilot-win32-arm64": "1.0.47",
+ "@github/copilot-win32-x64": "1.0.47"
}
},
"node_modules/@github/copilot-darwin-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.46.tgz",
- "integrity": "sha512-zbhXuRguCdDgeIZKH+rjgBM/6CDMUmhLMck8w9XFDxUY2wrP7MSWXuX8yA4/1H3ySOTZMIH1G5DQpWh+npmR2Q==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.47.tgz",
+ "integrity": "sha512-sGuN+7VfBjOTbPkyKFm0dPfp1hwyNsJVkNsV+3xmOwVsGy3nhROc76sQ5SWWSmyDGl7H58KnpPazlSDwbpf4PQ==",
"cpu": [
"arm64"
],
@@ -499,9 +499,9 @@
}
},
"node_modules/@github/copilot-darwin-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.46.tgz",
- "integrity": "sha512-kSUcV6cARhM+b/BuNSQtazbORTetRjIWpO3SqWSmH+2UoeZP5A5x+ipr7mhshq+E+pcWPeQKMGbKGY3lrCSMFw==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.47.tgz",
+ "integrity": "sha512-nVHYbzvOau5zy4nONWZPXROIrqzd7DhY12bMkE7spLe7lj0Sh6MFtTdPpMT7kkaObEikGYLTrZtOUpguwqHkmA==",
"cpu": [
"x64"
],
@@ -516,9 +516,9 @@
}
},
"node_modules/@github/copilot-linux-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.46.tgz",
- "integrity": "sha512-Tz3F0LuGFbOvvv0VKQJ4E5XYBsTdqTNMAwOhbkwX6TuKMX88uLJNKP5uPf6yuu1z3J3nt/5rfEd9CxVrZbnqLA==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.47.tgz",
+ "integrity": "sha512-7aDoE6pnSGcCTuPdJKyHfzif/Rj1z5UE0gLMHHQMo1QIYJkUZFX7mV8Ng4zB+2edq8lNL5DiYRcbFajV54ibSg==",
"cpu": [
"arm64"
],
@@ -533,9 +533,9 @@
}
},
"node_modules/@github/copilot-linux-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.46.tgz",
- "integrity": "sha512-s9JWe/YE78I7QEeXrvDGHB5x2XnnkegUJYVE9QR2DI/qLXviHMarM3akOUhed21uVqzoiLPacXKZcTcaDO8tOg==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.47.tgz",
+ "integrity": "sha512-wB5ekOdoxM/6Ogguk54fqJTHTRJkXwUIyzrbYaMy7zANE82jeRE1PQqs+5SdUZXq2IBMZIN1vq6bM56gpb54qg==",
"cpu": [
"x64"
],
@@ -550,9 +550,9 @@
}
},
"node_modules/@github/copilot-win32-arm64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.46.tgz",
- "integrity": "sha512-auX8o8vG8A+rdSthvey1D8q3o6lNlNIfHFjoBU0Z9Fxid6Ghz2paaAn0/Uwz9Ev8W8cn/5C5kEPs3niMXSh4Jw==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.47.tgz",
+ "integrity": "sha512-AenPXpTeXApOh25biS+Vmc1Uau78OLHxeXjXDF6Po07xWO7fVzorEK0hnSoD6xmpjptvP2MDSMk4as7jyvM0sQ==",
"cpu": [
"arm64"
],
@@ -567,9 +567,9 @@
}
},
"node_modules/@github/copilot-win32-x64": {
- "version": "1.0.46",
- "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.46.tgz",
- "integrity": "sha512-iXo9TUqtSxqlBfC+SZSQMrctKJpWR19zr+8dk7hczE42gOVB0/A+NySJwCmY3UFAEY98lbLDjIC+NCbYFcpEHA==",
+ "version": "1.0.47",
+ "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.47.tgz",
+ "integrity": "sha512-35bOBTTIm31rgbvFDogAMojWMSV6sLTd3mGjLl1Lf/d0KZGCGLqWXAYMAcV3grEjiAEXxlLLzNs8OfBR/9OdZg==",
"cpu": [
"x64"
],
diff --git a/test/harness/package.json b/test/harness/package.json
index 3df463311..e6c67908d 100644
--- a/test/harness/package.json
+++ b/test/harness/package.json
@@ -11,7 +11,7 @@
"test": "vitest run"
},
"devDependencies": {
- "@github/copilot": "^1.0.46",
+ "@github/copilot": "^1.0.47",
"@modelcontextprotocol/sdk": "^1.26.0",
"@types/node": "^25.3.3",
"@types/node-forge": "^1.3.14",