Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 198 additions & 1 deletion dotnet/src/Generated/SessionEvents.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1528,7 +1528,7 @@ public partial class PermissionRequestedData
public required string RequestId { get; set; }

[JsonPropertyName("permissionRequest")]
public required object PermissionRequest { get; set; }
public required PermissionRequest PermissionRequest { get; set; }
}

public partial class PermissionCompletedData
Expand Down Expand Up @@ -2095,6 +2095,193 @@ public partial class SystemMessageDataMetadata
public Dictionary<string, object>? Variables { get; set; }
}

public partial class PermissionRequestShellCommandsItem
{
[JsonPropertyName("identifier")]
public required string Identifier { get; set; }

[JsonPropertyName("readOnly")]
public required bool ReadOnly { get; set; }
}

public partial class PermissionRequestShellPossibleUrlsItem
{
[JsonPropertyName("url")]
public required string Url { get; set; }
}

public partial class PermissionRequestShell : PermissionRequest
{
[JsonIgnore]
public override string Kind => "shell";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("fullCommandText")]
public required string FullCommandText { get; set; }

[JsonPropertyName("intention")]
public required string Intention { get; set; }

[JsonPropertyName("commands")]
public required PermissionRequestShellCommandsItem[] Commands { get; set; }

[JsonPropertyName("possiblePaths")]
public required string[] PossiblePaths { get; set; }

[JsonPropertyName("possibleUrls")]
public required PermissionRequestShellPossibleUrlsItem[] PossibleUrls { get; set; }

[JsonPropertyName("hasWriteFileRedirection")]
public required bool HasWriteFileRedirection { get; set; }

[JsonPropertyName("canOfferSessionApproval")]
public required bool CanOfferSessionApproval { get; set; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("warning")]
public string? Warning { get; set; }
}

public partial class PermissionRequestWrite : PermissionRequest
{
[JsonIgnore]
public override string Kind => "write";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("intention")]
public required string Intention { get; set; }

[JsonPropertyName("fileName")]
public required string FileName { get; set; }

[JsonPropertyName("diff")]
public required string Diff { get; set; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("newFileContents")]
public string? NewFileContents { get; set; }
}

public partial class PermissionRequestRead : PermissionRequest
{
[JsonIgnore]
public override string Kind => "read";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("intention")]
public required string Intention { get; set; }

[JsonPropertyName("path")]
public required string Path { get; set; }
}

public partial class PermissionRequestMcp : PermissionRequest
{
[JsonIgnore]
public override string Kind => "mcp";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("serverName")]
public required string ServerName { get; set; }

[JsonPropertyName("toolName")]
public required string ToolName { get; set; }

[JsonPropertyName("toolTitle")]
public required string ToolTitle { get; set; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("args")]
public object? Args { get; set; }

[JsonPropertyName("readOnly")]
public required bool ReadOnly { get; set; }
}

public partial class PermissionRequestUrl : PermissionRequest
{
[JsonIgnore]
public override string Kind => "url";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("intention")]
public required string Intention { get; set; }

[JsonPropertyName("url")]
public required string Url { get; set; }
}

public partial class PermissionRequestMemory : PermissionRequest
{
[JsonIgnore]
public override string Kind => "memory";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("subject")]
public required string Subject { get; set; }

[JsonPropertyName("fact")]
public required string Fact { get; set; }

[JsonPropertyName("citations")]
public required string Citations { get; set; }
}

public partial class PermissionRequestCustomTool : PermissionRequest
{
[JsonIgnore]
public override string Kind => "custom-tool";

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

[JsonPropertyName("toolName")]
public required string ToolName { get; set; }

[JsonPropertyName("toolDescription")]
public required string ToolDescription { get; set; }

[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[JsonPropertyName("args")]
public object? Args { get; set; }
}

[JsonPolymorphic(
TypeDiscriminatorPropertyName = "kind",
UnknownDerivedTypeHandling = JsonUnknownDerivedTypeHandling.FallBackToBaseType)]
[JsonDerivedType(typeof(PermissionRequestShell), "shell")]
[JsonDerivedType(typeof(PermissionRequestWrite), "write")]
[JsonDerivedType(typeof(PermissionRequestRead), "read")]
[JsonDerivedType(typeof(PermissionRequestMcp), "mcp")]
[JsonDerivedType(typeof(PermissionRequestUrl), "url")]
[JsonDerivedType(typeof(PermissionRequestMemory), "memory")]
[JsonDerivedType(typeof(PermissionRequestCustomTool), "custom-tool")]
public partial class PermissionRequest
{
[JsonPropertyName("kind")]
public virtual string Kind { get; set; } = string.Empty;
}


public partial class PermissionCompletedDataResult
{
[JsonPropertyName("kind")]
Expand Down Expand Up @@ -2273,6 +2460,16 @@ public enum PermissionCompletedDataResultKind
[JsonSerializable(typeof(PermissionCompletedData))]
[JsonSerializable(typeof(PermissionCompletedDataResult))]
[JsonSerializable(typeof(PermissionCompletedEvent))]
[JsonSerializable(typeof(PermissionRequest))]
[JsonSerializable(typeof(PermissionRequestCustomTool))]
[JsonSerializable(typeof(PermissionRequestMcp))]
[JsonSerializable(typeof(PermissionRequestMemory))]
[JsonSerializable(typeof(PermissionRequestRead))]
[JsonSerializable(typeof(PermissionRequestShell))]
[JsonSerializable(typeof(PermissionRequestShellCommandsItem))]
[JsonSerializable(typeof(PermissionRequestShellPossibleUrlsItem))]
[JsonSerializable(typeof(PermissionRequestUrl))]
[JsonSerializable(typeof(PermissionRequestWrite))]
[JsonSerializable(typeof(PermissionRequestedData))]
[JsonSerializable(typeof(PermissionRequestedEvent))]
[JsonSerializable(typeof(SessionCompactionCompleteData))]
Expand Down
18 changes: 3 additions & 15 deletions dotnet/src/Session.cs
Original file line number Diff line number Diff line change
Expand Up @@ -339,7 +339,7 @@ internal async Task<PermissionRequestResult> HandlePermissionRequestAsync(JsonEl
};
}

var request = JsonSerializer.Deserialize(permissionRequestData.GetRawText(), SessionJsonContext.Default.PermissionRequest)
var request = JsonSerializer.Deserialize(permissionRequestData.GetRawText(), SessionEventsJsonContext.Default.PermissionRequest)
?? throw new InvalidOperationException("Failed to deserialize permission request");

var invocation = new PermissionInvocation
Expand Down Expand Up @@ -457,27 +457,16 @@ private async Task ExecuteToolAndRespondAsync(string requestId, string toolName,
/// <summary>
/// Executes a permission handler and sends the result back via the HandlePendingPermissionRequest RPC.
/// </summary>
private async Task ExecutePermissionAndRespondAsync(string requestId, object permissionRequestData, PermissionRequestHandler handler)
private async Task ExecutePermissionAndRespondAsync(string requestId, PermissionRequest permissionRequest, PermissionRequestHandler handler)
{
try
{
// PermissionRequestedData.PermissionRequest is typed as `object` in generated code,
// but StreamJsonRpc deserializes it as a JsonElement.
if (permissionRequestData is not JsonElement permJsonElement)
{
throw new InvalidOperationException(
$"Permission request data must be a {nameof(JsonElement)}; received {permissionRequestData.GetType().Name}");
}

var request = JsonSerializer.Deserialize(permJsonElement.GetRawText(), SessionJsonContext.Default.PermissionRequest)
?? throw new InvalidOperationException("Failed to deserialize permission request");

var invocation = new PermissionInvocation
{
SessionId = SessionId
};

var result = await handler(request, invocation);
var result = await handler(permissionRequest, invocation);
await Rpc.Permissions.HandlePendingPermissionRequestAsync(requestId, result);
}
catch (Exception)
Expand Down Expand Up @@ -780,7 +769,6 @@ internal record SessionDestroyRequest
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull)]
[JsonSerializable(typeof(GetMessagesRequest))]
[JsonSerializable(typeof(GetMessagesResponse))]
[JsonSerializable(typeof(PermissionRequest))]
[JsonSerializable(typeof(SendMessageRequest))]
[JsonSerializable(typeof(SendMessageResponse))]
[JsonSerializable(typeof(SessionAbortRequest))]
Expand Down
33 changes: 0 additions & 33 deletions dotnet/src/Types.cs
Original file line number Diff line number Diff line change
Expand Up @@ -266,38 +266,6 @@ public class ToolInvocation
/// </summary>
public delegate Task<object?> ToolHandler(ToolInvocation invocation);

/// <summary>
/// Represents a permission request from the server for a tool operation.
/// </summary>
public class PermissionRequest
{
/// <summary>
/// Kind of permission being requested.
/// <list type="bullet">
/// <item><description><c>"shell"</c> — execute a shell command.</description></item>
/// <item><description><c>"write"</c> — write to a file.</description></item>
/// <item><description><c>"read"</c> — read a file.</description></item>
/// <item><description><c>"mcp"</c> — invoke an MCP server tool.</description></item>
/// <item><description><c>"url"</c> — access a URL.</description></item>
/// <item><description><c>"custom-tool"</c> — invoke a custom tool.</description></item>
/// </list>
/// </summary>
[JsonPropertyName("kind")]
public string Kind { get; set; } = string.Empty;

/// <summary>
/// Identifier of the tool call that triggered the permission request.
/// </summary>
[JsonPropertyName("toolCallId")]
public string? ToolCallId { get; set; }

/// <summary>
/// Additional properties not explicitly modeled.
/// </summary>
[JsonExtensionData]
public Dictionary<string, object>? ExtensionData { get; set; }
}

/// <summary>Describes the kind of a permission request result.</summary>
[JsonConverter(typeof(PermissionRequestResultKind.Converter))]
[DebuggerDisplay("{Value,nq}")]
Expand Down Expand Up @@ -2005,7 +1973,6 @@ public class SetForegroundSessionResponse
[JsonSerializable(typeof(ModelPolicy))]
[JsonSerializable(typeof(ModelSupports))]
[JsonSerializable(typeof(ModelVisionLimits))]
[JsonSerializable(typeof(PermissionRequest))]
[JsonSerializable(typeof(PermissionRequestResult))]
[JsonSerializable(typeof(PingRequest))]
[JsonSerializable(typeof(PingResponse))]
Expand Down
2 changes: 1 addition & 1 deletion dotnet/test/PermissionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ public async Task Should_Receive_ToolCallId_In_Permission_Requests()
{
OnPermissionRequest = (request, invocation) =>
{
if (!string.IsNullOrEmpty(request.ToolCallId))
if (request is PermissionRequestShell shell && !string.IsNullOrEmpty(shell.ToolCallId))
{
receivedToolCallId = true;
}
Expand Down
6 changes: 2 additions & 4 deletions dotnet/test/ToolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,11 +237,9 @@ await session.SendAsync(new MessageOptions
Assert.Contains("HELLO", assistantMessage!.Data.Content ?? string.Empty);

// Should have received a custom-tool permission request with the correct tool name
var customToolRequest = permissionRequests.FirstOrDefault(r => r.Kind == "custom-tool");
var customToolRequest = permissionRequests.OfType<PermissionRequestCustomTool>().FirstOrDefault();
Assert.NotNull(customToolRequest);
Assert.True(customToolRequest!.ExtensionData?.ContainsKey("toolName") ?? false);
var toolName = ((JsonElement)customToolRequest.ExtensionData!["toolName"]).GetString();
Assert.Equal("encrypt_string", toolName);
Assert.Equal("encrypt_string", customToolRequest!.ToolName);

[Description("Encrypts a string")]
static string EncryptStringForPermission([Description("String to encrypt")] string input)
Expand Down
Loading
Loading